In questo articolo faremo una breve carrellata dei concetti base dello standard OSGi per introdurre il progetto Apache Karaf, che è un contenitore runtime, basato appunto su OSGi, in cui è possibile implementare applicazioni modulari. Mostreremo come installare e lanciare Karaf e ci soffermeremo sui alcuni dettagli specifici, come per esempio la Console, le features, l’Hot Deploy e la Dynamic Configuration.
Introduzione
Dal 2009, anno in cui ho incontrato per la prima volta lo standard OSGi, il percorso è stato in salita e spesso mi sono chiesto se l’OSGi fosse davvero la risposta giusta al problema della modularità; in fondo basterebbero le nozioni della programmazione a oggetti, i concetti delle Service Oriented Architecture e le definizioni degli Enterprice Integration Patterns. In realtà questi dubbi nascevano dal fatto che, all’inizio, la mia visione del problema e degli strumenti messi a disposizione era parziale.
Nella mia esperienza personale, l’OSGi si è dimostrato essere la piattaforma ideale per lo sviluppo di applicazioni enterprise manutenibili. Sono convinto che l’OSGi fornisce gli strumenti giusti per la realizzazione di architetture complesse, poiche’ consente di mescolare tra loro gli aspetti della modularità, migliorandone l’integrazione e fornendo funzionalità di manutenzione.
Con questo articolo vorrei orientare i lettori verso una visione chiara fin dal principio della piattaforma e cercare di incastrare tra loro i tasselli del mosaico, seguendo un filo il più possibile lineare. Come consiglio ai lettori, suggerisco di immaginare una possibile applicazione e realizzarla facendo tesoro delle nozioni presentate in questo articolo.
Il primo nodo che sarà affrontato è quello di modularità, uno dei concetti più abusati del mondo dell’informatica, del quale daremo una definizione per arginare le ambiguità che questo concetto porta con se.
Introdurremo lo standard OSGi che fornisce un framework di sviluppo basato su componenti modulari e disaccoppiati e su un modello di servizi dinamico.
E infine presenteremo Karaf, una piattaforma per lo sviluppo modulare che integra nel framework OSGi numerose funzionalità che permettono la gestione del ciclo di vita dei moduli, dei servizi, della configurazione, delle feature, del logging.
Modularity
Iniziamo col dire che esiste una modularità a run-time ed una a build-time. In questo articolo ci concentreremo sulla definizione di modularità a run-time poiche’ l’OSGi offre questo tipo di funzionalità.
“A software module is a deployable, manageable, natively reusable, composable, stateless unit of software that provides a concise interface to consumers” [1]
I moduli, a differenza di altre entità come le classi o i package, sono unità discrete della distribuzione che possono essere affiancate da altri moduli software. Cioè i moduli sono entità più “fisiche” rispetto alle classi o ai package. Un esempio di modulo può essere un file JAR.
Un modulo può essere gestito nel senso che può essere installato, disinstallato o aggiornato. Durante le fasi di sviluppo, la divisione in moduli può aiutare a pianificare e a parallelizzare le attività.
Un modulo è un’entità testabile indipendente ed è pensato per essere riusato da più processi, ma le operazioni che espone possono essere invocate solo da chiamate dirette a metodo.
I moduli sono unità componibili tra loro. Può succedere che un macro-modulo sia composto da più micro-moduli.
Esiste una sola istanza di una specifica versione del modulo. Le classi definite nel modulo vengono istanziate e mantengono il loro stato. I moduli invece sono stateless.
Figura 1 – Massimizzare la flessibilità, gestendo la complessità.
Trovare il compromesso fra flessibilità e complessità
Per spiegare il grafico in figura 1 facciamo un ragionamento di tipo pratico: supponiamo di dover scrivere un software che permetta di applicare modifiche future in maniera semplice e con pochi costi aggiuntivi. Quello che voglio è che questo software sia il più flessibile possibile, ma la flessibilità ha un costo che si paga con la complessità. Siamo in presenza di un paradosso: per ridurre i costi delle modifiche al mio software dovrei renderlo più flessibile, ma così facendo lo rendo più complesso e quindi aumento i costi di manutenzione. Il grafico mostra come, introducendo il concetto di modularità nel software, la pendenza della curva cambia riducendo la complessità a parità di flessibilità. Questo ragionamento si conclude con l’assioma: “Modularity is the best hope to reduce the cost of changes due to architectural designing” [1].
OSGi (Open Service Gatway initiave)
Lo standard OSGi nasce dal lavoro svolto dall’OSGi Alliance, un’organizzazione fondata nel 1999 da Ericsson, IBM, Oracle e altri partner minori, che “propone un processo collaudato e maturo per creare specifiche aperte che permettono una composizione modulare del software integrato con la tecnologia Java” [2]. Iniziamo a vedere nel dettaglio quali sono gli aspetti salienti partendo dall’analisi di queste specifiche, oggi giunte alla quinta versione.
Figura 2 – I layer dell’architettura OSGi.
Il framework è composto da tre strati sovrapposti tra loro: Module, Lifecycle, Service.
Module layer
Il primo strato è il Module layer. Le specifiche definiscono l’entità atomica della modularità: il bundle. Il bundle è un file JAR con delle caratteristiche speciali.
Figura 3 – Un bundle contiene i sorgenti, le risorse e il manifest.
La prima differenza tra un bundle e un semplice JAR è che il primo ha un classpath “limitato a se stesso”. In pratica il bundle vede solo le classi definite al suo interno. Questa, a prima vista, può sembrare una forte limitazione, ma in realtà è il vero vantaggio dell’OSGi; vediamo subito il perche’, analizzando la seconda differenza: il manifest file.
Il manifest file è esteso rispetto a quello di un JAR standard con headers aggiuntivi, raggruppati nelle specifiche OSGi in categorie quali identificazione e descrizione, classloading e attivazione.
Manifest-Version: 1.0 Bnd-LastModified: 1386750447262 Build-Jdk: 1.7.0_40 Built-By: gdg-firenze Bundle-ManifestVersion: 2 Bundle-Name: GDG Firenze :: Sensormix :: Example Bundle Bundle-SymbolicName: example-bundle Bundle-Vendor: GDG Firenze :: Sensormix Team Bundle-Version: 1.0.0.SNAPSHOT Bundle-Activator: com.google.developers.gdgfirenze.dataservice.Activator Created-By: Apache Maven Bundle Plugin Export-Package: com.google.developers.gdgfirenze.model;version="1.0.0.SNAPSHOT", com.google.developers.gdgfirenze.osgi;version="1.0.0.SNAPSHOT", com.google.developers.gdgfirenze.service;version="1.0.0.SNAPSHOT" Import-Package: javax.jws,javax.jws.soap,javax.xml.bind.annotation,javax.xml.ws Tool: Bnd-1.50.0
La categoria identificazione e descrizione comprende header quali Bundle-Name e Bundle-Version che si usano per definire il nome del bundle e la sua versione.
La categoria attivazione comprende header come il Bundle-Activator che permette di definire quale classe all’interno del bundle sarà avviata automaticamente durante la fase di start-up del bundle.
Infine la categoria di classloading comprende header quali Export-Package e Import-Package e permette di definire quali package mettere a disposizione dei bundle esterni e soprattutto quali package devono essere importati perche’ necessari alla “vita” del bundle. In sostanza, tramite il manifest, possiamo estendere la visibilità del classloading del bundle, permettendo l’utilizzo di classi definite in altri moduli.
Ecco la svolta: per poter usare classi di altri bundle devo definire esplicitamente la dipendenza. Questo concetto, per quanto semplice, è estremamente importante perche’ permette di definire con precisione i confini del bundle.
Lifecycle layer
Una volta definita l’unità atomica della modularità, passiamo ad analizzare il suo ciclo di vita, introducendo il secondo strato del framework: il Lifeycle layer. Le API di questo strato definiscono un ciclo di vita per il bundle mostrato nella Figura 3.
Figura 4 – Il ciclo di vita di un bundle.
Dopo aver installato il bundle, il framework analizza il manifest alla ricerca degli headers della categoria identificazione e descrizione. Qui vengono fatti check di congruenza e di versione. Se l’identificazione è corretta, il bundle va in stato Installed. Il framework, a questo punto, ispeziona il manifest alla ricerca della sezione di classloading. In questa fase vengono risolte le dipendenze dichiarate nel manifest verificando che i package definiti nell’header Import-Package siano disponibili. Solo quando tutte le dipendenze del bundle sono state risolte, il bundle avanza nello stato successivo, ossia in stato Resolved. A questo punto il framework analizza la categoria attivazione per allocare un’istanza della classe che implementa l’interfaccia BundleActivator, passa lo stato del bundle in Starting e chiama il metodo start(). Se tale metodo termina senza eccezioni lo stato del bundle diviene Active. L’interfaccia BundleActivator definisce anche il motodo stop() che sarà invocato quando richiesto. Se lo stop() va a buon fine il bundle ritorna in stato Resolved.
Le API del Lifecycle layer definiscono anche il concetto di BundleContext che permette al bundle di interagire con il framework non solo per ciò che riguarda il ciclo di vita del bundle ma soprattutto per la gestione dei servizi. Per approfondimenti su questi concetti, vi rimandiamo al libro [3].
Service layer
A questo punto possiamo passare all’ultimo strato: il Service layer. Le API di questo layer non definiscono il Servizio (assumiamo quindi che il Servizio sia quello definito per le SOA) ma piuttosto definiscono il ciclo di vita dei servizi e le modalità delle loro interazioni.
public interface FileManagerService { void open(String filename); void save(); void close(); }
Supponiamo di aver definito un’interfaccia Java che espone dei metodi open, save, print e di aver realizzato l’implementazione di questa interfaccia che applica queste operazioni ad un file in formato ASCII (.txt). Ora vogliamo che altri oggetti, magari scritti da altri o che ancora non sono stati scritti, possano usufruire delle funzionalità offerte dal nostro servizio.
Faccio qui un inciso che, oltre ad evidenziare un concetto essenziale per lo sviluppo di moduli, vuole essere anche un “trick” per il lettore. Abbiamo detto che gli altri bundle non possono vedere la nostra interfaccia se non hanno espressamente dichiarato la dipendenza nel loro manifest. Non è certamente una buona pratica introdurre dipendenze tra un bundle che espone un servizio ed un altro che lo usa. La cosa da fare è quella di definire l’interfaccia in un bundle separato. In questo modo gli altri bundle potranno utilizzare i metodi definiti nell’interfaccia senza avere dipendenze dirette dal bundle che contiene l’implementazione.
A questo punto si pone il problema di come ottenere a runtime un’istanza del servizio. Il Service Layer offre degli strumenti per risolvere questo problema.
Figura 5 – Il modello di interazione service oriented.
Il Service Provider, ossia bundle che implementa l’interfaccia Java da noi definita, registrerà l’istanza del servizio al Service Registry utilizzando il metodo registerService del BundleContext, che come abbiamo detto prima, fornisce delle funzionalità per interagire con il framework. Il Service Requestor, ossia un altro bundle che vorrà usare la suddetta implementazione, richiederà, quando necessario, l’istanza del servizio al Service Registry. Tutte le interazioni ruotano attorno al concetto di Service Descrition, ossia l’interfaccia del servizio.
Le richieste possono essere di vario tipo a seconda delle esigenze. Per esempio, nel caso in cui il Service Registry non conoscesse l’istanza per una specifica Description, sarà possibile gestire in diversi modi lo scenario. Se la funzionalità richiesta è necessaria per un’operazione real time, sarà possibile programmare il nostro bundle in modo da lanciare un’eccezione. L’eccezione potrà essere gestita così da non compromettere l’esecuzione dell’applicazione. Se invece la funzionalità è richiesta per un’operazione batch, potremmo decidere di aspettare fintanto che il servizio non sarà disponibile. Rimandiamo al libro [3] paragrafo “4.3.2 Listening for services”, per approfondimenti su questi aspetti.
OSGi Standard Services
Per concludere questa carrellata sull’OSGi, introduciamo l’ultimo tassello che ancora manca.
Figura 6 – La piattaforma a servizi OSGi.
L’OSGi non definisce solo le API del framework, ma introduce anche il primo insieme di servizi standard per dare degli strumenti che consentano allo sviluppatore di sfruttare la modularità e al manutentore di poterla gestire. I servizi più importanti definiti dall’OSGi sono il Cofiguration Admin che consente di gestire la hot configuration, l’Event Admin che permette di avere un Bus di eventi senza utilizzare librerie esterne, il Console Admin che permette sia di interagire con il framework ma anche di definire dei comandi per interagire con i propri servizi, il Log Service che accentra tutte le funzionalità di logging e il Blueprint component framework per gestire la dependency injection in maniera similare a Spring attraverso l’uso di “beans” definiti in file XML.
Karaf: un container OSGi container
Nell’articolo precedente [4] è stato spiegato come Karaf nasca da un’estrapolazione del kernel di ServiceMix. In questo articolo vorrei dimenticare le ragioni storiche e soffermarmi su quelle tecniche che a mio avviso sono altrettanto importanti. Karaf è a tutti gli effetti un’applicazione modulare basata sul framework OSGi. Vediamo il perche’.
Per prima cosa scarichiamo l’ultima versione stabile, la 2.3.4, dal sito ufficiale [6]. Karaf non ha bisogno di installazione ed è sufficiente decomprimere l’archivio in una cartella nel nostro file system. La figura 6 illustra l’alberatura dei file estratti dall’archivio.
Figura 7 – L’alberatura della cartella di Karaf.
La strutura di Karaf
I file per avviare Karaf sono contenuti nella cartella bin, mentre i file di configurazione nella cartella etc. Una volta avviato Karaf, l’implementazione del servizio Configuration Admin tiene sotto controllo i file che si trovano in questa cartella e, utilizzando le API del Lifecycle Layer, può notificare ai bundle le modifiche alla configurazione. La cartella deploy è inizialmente vuota e permette di gestire la “installazione a caldo” dei bundle. Copiando all’interno di deploy un bundle, il framework lo lancerà utilizzando le procedure per il Lifecycle Layer viste in precedenza. Poco prima abbiamo affermato che Karaf è un’applicazione OSGi e quindi ci aspetteremmo che sia fatta di moduli. E infatti nella cartella system ci sono i bundle di sistema di Karaf (framework, logging, etc.)
Proviamo adesso a lanciare Karaf: entriamo nella cartella bin ed eseguiamo karaf.bat se siamo su Windows, o semplicemente karaf se siamo in ambiente Unix. In figura 8 è mostrato quello che apparirà sullo schermo.
Figura 8 – La console di Karaf al primo avvio.
Karaf Console
Tra i servizi di sistema di Karaf c’è il Console Admin che ci permette di interagire con il framework attraverso dei comandi. Digitando il comando help, ci verranno mostrati tutti i comandi accettati dalla console. I comandi sono suddivisi per funzionalità:
- osgi: raggruppa l’inseme dei comandi di più basso livello per l’interazione con OSGi;
- config: è l’insieme dei comandi per interagire con il servizio di configurazione dichiarando nuove variabili di configurazione o modificando il valore di variabili esistenti;
- admin: raggruppa i comandi per la gestione della console;
- features: è l’insieme dei comandi che permettono di gestire le features, una funzionalità molto utile di Karaf.
Per ogni famiglia di comandi è possibile richiamare l’help specifico per i singoli comandi: per esempio con help features. Una funzionalità della console molto comoda è il completamento automatico, utilizzabile con il tasto Tab, che ci agevola nella digitazione dei comandi.
Digitando il comando list (il token osgi: può essere sottinteso) è possibile vedere la lista dei bundle non di sistema installati. Poiche’ siamo al primo lancio, la lista risulterà vuota. Digitiamo allora list -t 0. In questo modo vedremo tutti i bundle installati compresi quelli di sistema.
Figura 9 – L’output del comando list -t 0.
Nella lista viene mostrato l’id del bundle, che è un id univoco e viene incrementato ogni volta che si installa un bundle. Abbiamo lo State del bundle che rispecchia fedelmente quanto detto prima a proposito del lifecycle layer. Abbiamo poi la colonna Blueprint, che mostra lo stato dei servizi creati utilizzando l’estensione del framework. La colonna Level dà indicazione circa l’ordine con cui il bundle viene elaborato. Esistono 100 livelli (da 0 a 99) e il framework all’avvio processa prima quelli a livello più basso. E infine c’è la colonna Name che è il nome del bundle indicato nel Manifest alla voce Bundle-Name.
Guardiamo insieme la lista dei bundle di sistema. Possiamo subito evidenziare il bundle con id 3, OPS4J Pax Logging – Service, che è l’implementazione OPS4J del servizio di logging definito da OSGi. In pratica, il servizio fornisce un logging centralizzato e configurabile che, da un lato è compatibile con tutte le principali interfacce di logging (java.util.logging, slf4j) e dall’altra utilizza l’ormai consolidata libreria log4j per la gestione del logging. Il bundle con id 5, invece, è il bundle che installa il servizio di gestione della configurazione; in questo caso è l’implementazione Apache Felix.
Potremmo dilungarci nell’analisi, ma quello che vogliamo evidenziare è che queste sono implementazioni di servizi definiti dal framework e che, volendo, possono essere sostituiti con altre implementazioni, magari scritte da noi, in maniera trasparente. E questa è l’essenza della modularità OSGi.
Feature
A questo punto proviamo a installare un bundle; per esempio la webconsole. Karaf consente di installare bundle utilizzando la convenzione maven per identificare le sue coordinate. Dalla console digitiamo
install mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.3.4.
Figura 10 – L’errore visualizzato dalla console se l’installazione fallisce.
Riceviamo un messaggio di errore perche’ le dipendenze del bundle che vogliamo installare non vengono risolte e il bundle non viene installato. Poiche’ alcuni bundle possono avere molte dipendenze, è scomodo doverle installare a mano. Karaf allora ci fornisce le features, ossia una modalità per raggruppare i bundle e installarli tutti insieme. Per la webconsole esiste una feature che riportiamo di seguito.
http://karaf.apache.org/xmlns/features/v1.0.0"> ... realm=karaf role=${karaf.admin.role} http mvn:org.apache.felix/org.apache.felix.metatype/1.0.10 mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.branding/2.3.4 mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.console/2.3.4 webconsole-base mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.admin/2.3.4 mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.features/2.3.4 mvn:org.apache.karaf.webconsole/org.apache.karaf.webconsole.gogo/2.3.4 mvn:org.apache.felix/org.apache.felix.webconsole.plugins.event/1.1.0 ...
Per installarla usiamo il comando features:install webconsole. Non riceviamo messaggi di errore e digitando list -t 0 in fondo alla lista troveremo i 6 bundle installati dalla feature. Con questa funzionalità chi sviluppa può definire “il pacchetto di installazione” rendendo trasparente a chi installa le problematiche legate alle dipendenze.
Webconsole
Vale la pena fare una breve descrizione della web console che abbiamo appena installato. Apriamo il browser e digitiamo il link http://localhost:8181/system/console/ (utilizzando karaf come nome utente e password). La pagina aperta mostrerà la lista dei bundle installati simile al comando list della console, con una colonna aggiuntiva Actions: questa colonna consente di stoppare, riavviare, aggiornare e disinstallare un bundle. Possiamo anche installare nuovi bundle utilizzando il bottone Install/Update e selezionandoli dal filesystem. È anche possibile filtrare la lista utilizzando l’apposita sezione (equivalente del | grep applicato al comando list da console). Facendo click sul nome del bundle si aprirà la pagina di dettaglio con l’elenco delle informazioni estrapolate dal manifest e quelle relative allo stato del bundle.
Figura 11 – La pagina della webconsole con le informazioni di dettaglio del bundle
In alto nella pagina sono presenti tre menù a tendina: Main, OSGi e Web Console.
Con la voce Main possiamo accedere alle pagine Admin che permette la gestione delle connessioni remote alla console in modalità ssh, Features che mostra la lista delle feature e permette l’installazione/disinstallazione delle features nonche’ l’aggiunta di nuove features non pre-configurate e Console che mostra la console all’interno della pagina web.
La voce OSGi permette di accedere alle pagine Bundles (prima pagina visualizzata), Configuration che mostra l’elenco dei bundle che si sono registrati al servizio Configuration Admin, Events che mostra gli eventi inviati/ricevuti tramite l’Event Admin, Log Service che mostra l’elenco degli eventi di log generati nel sistema e la pagina Services che mostra l’elenco di tutti i servizi registrati con accanto indicato il bundle che ha registrato il servizio stesso. Facendo click su uno degli id di servizio si aprirà la pagina di dettaglio dove viene mostrato l’elenco dei bundle che usano il servizio.
Figura 12 – La pagina della webconsole con le informazioni di dettaglio del servizio.
Grazie a queste funzionalità è molto agevole interagire con il framework, anche da remoto, e conoscere lo stato dei bundle e dei servizi potendo quindi operare le giuste azioni sia per gestire le normali manutenzioni del sistema sia per gestire situazioni di fault.
Configurazione repository
Torniamo ad analizzare la console di Karaf. Sia il comando install che il comando features:install scaricano i bundle da repository maven preconfigurati. È possibile ampliare la lista dei repository sia utilizzando il file di configurazione org.ops4j.pax.url.mvn.cfg nella cartella etc che il comando addurl.
Per quanto riguarda le features, la lista di quelle disponibili è pre-configurata e possiamo ampliarla aggiungendone di nuove nel file org.apache.karaf.features.cfg o utilizzando il comando features:addurl.
Chiaramente non tutti i bundle che creiamo possono essere pubblicati su un repository pubblico come quello maven, quindi potrebbe essere problematico installare bundle che sono solo nel nostro repository locale. Karaf risolve anche questo problema dando la possibilità di creare una cartella local-repo nella folder principale in cui copiare i nostri bundle “privati” rispettando l’alberatura maven.
Hot Deploy
Ma non è tutto: se, per esempio, siamo in fase di deploy e abbiamo bisogno di installare un bundle per provarlo e magari di sovrascriverlo più volte per risolvere i bug che riscontriamo, può essere complicato disinstallare e installare manualmente anche dal repository locale. Karaf allora ci permette di copiare i bundle nella cartella deploy e di installarli “al volo”. Lanciamo Karaf e, solo per comodità, prendiamo il bundle org.eclipse.persistence.jpa-2.5.0.jar, l’implementazione EclipseLink dello standard JPA, copiandolo nella cartella deploy.
Figura 13 – Bundle in stato Installed.
Digitando list verifichiamo che il bundle è stato installato, ma non tutte le sue dipendenze sono state risolte e quindi nella colonna State troviamo Installed. Per permettere la risoluzione di tutte le dipendenze è necessario copiare l’intero pacchetto EclipseLink, composta dal file di specifiche javax.persistence-2.1.0.jar e dai file org.eclipse.persistence.antlr-2.5.0.jar, org.eclipse.persistence.asm-2.5.0.jar, org.eclipse.persistence.core-2.5.0.jar, org.eclipse.persistence.jpa.jpql-2.5.0.jar. Solo dopo averli copiati tutti nella cartella deploy possiamo digitare list e verificare che tutti i bundle sono stati risolti e avviati.
Figura 14 – Bundles in stato Active.
Conclusioni
Abbiamo visto i motivi per cui Karaf si candida a essere una piattaforma modulare che ci consente di sfruttare tutte le potenzialità del framework OSGi. Il prodotto Karaf è maturo, è usato in ambito professionale e la community che lo sviluppa è viva e ogni giorno promuove nuove funzionalità.
Non ho la presunzione di dire che con questo articolo vi ho trasferito tutto ciò che c’è da dire sull’argomento, anzi. Alcuni argomenti sono stati solo accennati e altri neanche menzionati, e su Karaf c’è sempre da imparare, visto anche l’apporto continuo e stimolante della sua communitu.
Di una cosa però sono certo: Karaf è uno strumento e come tale è responsabilità di chi lo usa far risaltare i suoi pregi e smussare i suoi difetti. Un cacciavite “nelle mani giuste fa miracoli” ma in quelle sbagliate fa molti danni. Come sempre non è tutto bianco o nero ma ci sono numerose sfumature. L’abilità dell’architetto software e dello sviluppatore sta nel (ri)conoscerle e muoversi tra di esse.
Riferimenti
[1] K. Knoernschild “Java Application Architecture. Modularity patterns with examples using OSGi”, Prentice Hall
[2] OSGi.org
http://www.osgi.org/About/HomePage
[3] R.S. Hall – K. Pauls – S. McCulloch – D. Savage, “OSGi in Action”, Manning 2011
[4] Cristiano Costantini, “Java modulare con Apache Karaf. Un esempio pratico per una architettura alternativa”, MokaByte 193, Marzo 2014
https://www.mokabyte.it/2014/03/karaf-1/
[5] Cristiano Costantini – Giuseppe Gerla – Michele Ficarra – Sergio Ciampi – Stefano Cigheri, “SensorMix”
https://github.com/cristcost/sensormix
[6] Il sito di Karaf