sabato 22 settembre 2012

Il logging delle applicazioni: Log4j non banale

In questo articolo voglio descrivere alcuni concetti di configurazione di log4j per dimostrare come sia possibile ottenere un controllo molto fine e preciso del logging applicativo.

Il tema di “far scrivere dei messaggi significativi alle nostre applicazioni” penso sia noto a tutti tuttavia occorre capire se quello che scriviamo ha un senso ed una logica e come possiamo pilotare gli strumenti in nostro possesso per farli diventare una potente arma nelle nostre mani.


Mi è capitato spessissimo di trovare le solite “System.out.println” all’interno di svariate classi pagate a peso d’oro all’ennesima ditta di fornitori o comunque di trovare “log.info” o “log.debug” usati in maniera indistinta per segnalare qualunque tipo di messaggio/errore.

Ad oggi possiamo parlare di 4 framework principali per il logging in ambiente java: Log4j, Java Logging API, Apache Commons Logging e SLF4J.
A mio avviso il framework di riferimento rimane comunque log4j ed è su questo che ci concentreremo.

Assumo che il tema sia abbastanza noto, quindi volendo sintetizzare per i ritardatari:

  • Appender. Sono gli oggetti che mi permettono di definire una destinazione per i miei log, può essere lo standard output, un file su disco, un socket o altro ancora.
  • Logger. Sono gli oggetti grazie ai quali posso scrivere su una delle destinazioni. Un Logger si può associare ad una classe specifica o a tutte le classi di un package.
  • Level. Sono i valori di logging che possiamo assegnare ad un particolare messaggio tenendo conto che vale la seguente relazione: TRACE < DEBUG < INFO < WARN < ERROR < FATAL. Trace è la modalità di logging più verbosa che possiamo attivare mentre fatal ci permette di intercettare esclusivamente dei problemi “bloccanti”.

Quando usare un livello rispetto ad un altro? Questo tipicamente dipende dall’applicazione ma in generale la regola è la seguente:

  • Debug, per la fase di test e successivamente per permetterci di individuare problemi che si possano presentare una volta che il sistema è in produzione.
  • Info, per tracciare le informazioni di base atte a indicare che la nostra applicazione è Up&Running.
  • Warning, per tracciare quelle informazioni che può essere utile sapere perchè in contrasto con le regole generali dell’applicazione ma che non hanno particolari impatti.
  • Error, per tracciare gli errori importanti che è bene segnalare e che tipicamente vanno interpretati e corretti e comunque gestiti al meglio.
  • Fatal, per indicare delle problematiche bloccanti che non permettono la corretta esecuzione della nostra applicazione.

Facciamo un esempio in pseudo-codice:

try {
 log.info(“Attivazione connessione socket”);
 log.debug(“URL connessione: ”+url);
 log.debug(“User: ”+user);
 log.debug(“Password: ”+password);
 
 socket.connect(url, user, password);
 socket.send(data);

catch(SocketException se) {
 log.fatal(“impossibile stabilire una connessione”);
}
catch(AuthenticationException ae) {
 log.error(“Utente o password errati. Verrà attivata una sessione guest con minori privilegi”);
}
catch(DataValidationException dve) {
 log.warn(“Il buffer dati ha una lunghezza inferiore a quella attesa.”);
}

Fin qui penso di non aver dato nessun contributo significativo.

Vediamo ora una configurazione interessante che si può utilizzare per tenere sotto controllo le nostre applicazioni.
Supponiamo di avere un’applicazione con 3 classi alle quali siamo principalmente interessati chiamate rispettivamente LoggerTestA, LoggerTestB e LoggerTestC.
Immaginando di rilasciare in produzione la nostra applicazione, potrebbe essere utile tenere il logging in debug almeno per un pò in modo da avere tutti i log del caso qualora questo fosse necessario quindi predisporremo un rolling log che ci permette di tracciare i log verbosi senza preoccuparci di andare a saturare per disgrazia lo spazio disco del nostro server.
In aggiunta andremo a scrivere il normale logging di INFO sullo standard output e poichè siamo particolarmente interessati ai WARNING scatenati dalla classe LoggerTestB, le riserviamo un ulteriore file di log dedicato.

>> Note: idealmente i log in debug non andrebbero mai messi su un sistema in produzione ma nel mondo reale questa è una prassi che si trova abbastanza di frequente... NON dite di no sapendo di mentire!

# APPENDER
# STDOUT in info
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

# FILE CUSTOM in warning
log4j.appender.custom=org.apache.log4j.FileAppender
log4j.appender.custom.File=log/custom.log
log4j.appender.custom.layout=org.apache.log4j.PatternLayout
log4j.appender.custom.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

# ROLLING FILE in debug
log4j.appender.roll=org.apache.log4j.RollingFileAppender
log4j.appender.roll.File=log/roll.log
log4j.appender.roll.MaxFileSize=100KB
log4j.appender.roll.MaxBackupIndex=3
log4j.appender.roll.layout=org.apache.log4j.PatternLayout
log4j.appender.roll.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n

# LOGGER

log4j.rootLogger=INFO, stdout
log4j.logger.it.lcianci.test.log4j.LoggerTestB=WARN, custom
log4j.logger.it.lcianci.test.log4j.LoggerTestA=DEBUG, roll
log4j.logger.it.lcianci.test.log4j.LoggerTestB=DEBUG, roll
log4j.logger.it.lcianci.test.log4j.LoggerTestC=DEBUG, roll

Questo tipo di configurazione, ovvero mediante il file log4j.properties, avviene mediante l’utilizzo della classe PropertyConfigurator da qualche parte nella fase di startup della nostra applicazione:

PropertyConfigurator.configure("conf/log4j.properties");

Ecco quindi uno stralcio dei nostri log:

Standard output

2012-09-14 14:37:09,671 DEBUG LoggerTestA:15 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,673  INFO LoggerTestA:16 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,673  WARN LoggerTestA:17 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,673 ERROR LoggerTestA:18 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,674 FATAL LoggerTestA:19 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,674 DEBUG LoggerTestB:15 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,675  INFO LoggerTestB:16 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,675  WARN LoggerTestB:17 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,675 ERROR LoggerTestB:18 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,676 FATAL LoggerTestB:19 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,677 DEBUG LoggerTestC:15 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,677  INFO LoggerTestC:16 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,677  WARN LoggerTestC:17 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,677 ERROR LoggerTestC:18 - Logging da configurazione di tipo property file
2012-09-14 14:37:09,678 FATAL LoggerTestC:19 - Logging da configurazione di tipo property file

Custom log (LoggerTestB)

2012-09-14 14:34:47,916  WARN LoggerTestB:17 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,916 ERROR LoggerTestB:18 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,916 FATAL LoggerTestB:19 - Logging da configurazione di tipo property file

Rolling log

2012-09-14 14:34:47,912 DEBUG LoggerTestA:15 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,914  INFO LoggerTestA:16 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,914  WARN LoggerTestA:17 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,914 ERROR LoggerTestA:18 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,915 FATAL LoggerTestA:19 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,927 DEBUG LoggerTestC:15 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,927  INFO LoggerTestC:16 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,928  WARN LoggerTestC:17 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,928 ERROR LoggerTestC:18 - Logging da configurazione di tipo property file
2012-09-14 14:34:47,928 FATAL LoggerTestC:19 - Logging da configurazione di tipo property file

Il Configurator può anche essere utilizzato a partire da un file xml che ci permette di definire alcune altre utili proprietà di log4j presenti solo in questa modalità, mi riferisco ai filtri. L’utilizzo della configurazione mediante file xml presuppone la presenza, sulla nostra applicazione, del file DTD di log4j.

Il questo caso non usiamo il PropertyConfigurator ma il DOMConfigurator:

DOMConfigurator.configure("conf/log4j.xml");

Vediamo lo stesso esempio riadattato.

Supposto di avere sempre le nostre 3 classi LoggerTestA, B e C, ipotizziamo di voler gestire per nostra comodità i seguenti file di log:

  • un file di output con i messaggi standard informativi,
  • un file di errore per tracciare appunto errori e warning,
  • il file di debug sempre di tipo rolling per quanto spiegato in precedenza
  • un file dedicato per la classe LoggerTestA

Scarica qui il file log4j.xml e la DTD di log4j.


Infine uno stralcio dei nostri log:

Output log

2012-09-14 14:37:09  INFO LoggerTestA [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestA [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestA [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestA [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestB [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestB [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestB [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestB [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestC [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestC [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestC [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestC [17] - Logging da configurazione di tipo xml file

Error log

2012-09-14 14:37:09 ERROR LoggerTestA [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestA [19] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestB [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestB [19] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestC [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestC [19] - Logging da configurazione di tipo xml file

Debug log

2012-09-14 14:37:09 DEBUG LoggerTestA [15] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestA [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestA [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestA [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestA [19] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 DEBUG LoggerTestB [15] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestB [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestB [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestB [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestB [19] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 DEBUG LoggerTestC [15] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  INFO LoggerTestC [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestC [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestC [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestC [19] - Logging da configurazione di tipo xml file

Custom log (LoggerTestA)

2012-09-14 14:37:09  INFO LoggerTestA [16] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09  WARN LoggerTestA [17] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 ERROR LoggerTestA [18] - Logging da configurazione di tipo xml file
2012-09-14 14:37:09 FATAL LoggerTestA [19] - Logging da configurazione di tipo xml file

Chiaro il senso dei filtri?
In pratica possiamo definire il livello massimo e minimo di scrittura, a livello di singolo appender, riuscendo così a dirigere nel modo migliore l’output dell’applicazione secondo le nostre esigenze.


Riferimenti:

http://logging.apache.org/log4j/1.2/
http://it.wikipedia.org/wiki/Log4j

martedì 18 settembre 2012

Tutorial OSGI: deploy di una web application come bundle

In questo articolo vedremo come effettuare il deploy di una web application su JBoss 7.1.1 dopo averla trasformata in un bundle OSGI. In gergo si parla di modulo WAB (web archive bundle).

Preparate la vostra web application così come siete abituati a fare, ad esempio con il wizard di Eclipse per le “dynamic web application”. Create le vostre servlet e le vostre jsp. Aggiungete eventuali librerie esterne. Insomma procedete come fareste normalmente.

Una volta che il nostro modulo WAR è bello e pronto, andiamo a modificare il file manifest presente nella cartella META-INF. Ecco un esempio:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TestWeb
Bundle-SymbolicName: TestWeb
Bundle-Version: 1.0.0
Bundle-ClassPath: .,WEB-INF/classes/
Import-Package: javax.servlet.http;version="2.5", javax.servlet;version="2.5", org.osgi.framework;version="1.3.0"
Web-ContextPath: TestWeb

Come potete vedere, il manifest non presenta particolari differenze rispetto a quanto visto nel precedente tutorial. Anche in questo caso dobbiamo impostare il classpath, i package da importare e tutto il resto. Unica novità il “Web-ContextPath” che ovviamente definisce il context da richiamare per poter accedere all nostra applicazione.
State attenti che, da quanto ho capito, al momento i WAB non funzionano con le specifiche 3.0 delle Servlet, quindi occorre usare le specifiche 2.5 e utilizzare il web.xml.
Per il resto non avete altro da fare alla vostra applicazione web, per trasformarla in un WAB OSGI occorre solo aggiongere le meta informazioni nel file manifest.

Resta adesso un piccolo punto.
JBoss 7 espone i WAB su Jetty alla porta 8090 quindi dovete attivare il server per poter così accedere alla vostra applicazione su http://localhost:8090/TestWeb.
Per fare questo è sufficiente modificare la configurazione del “subsystem” urn:jboss:domain:osgi:1.2 all’interno del file “standalone.xml” attivando alcune “capability”.
In particolare:

capability name="org.ops4j.pax.web:pax-web-jetty-bundle:1.1.2" startlevel="1"
capability name="org.ops4j.pax.web:pax-web-jsp:1.1.2" startlevel="1"
capability name="org.ops4j.pax.web:pax-web-extender-war:1.1.2" startlevel="1"

Il gioco è fatto. Se adesso effettuate il deploy del WAB, lo troverete in stato active all’interno dei bundle OSGI e potrete collegarvi alla porta 8090 per accedere alla web applilcation.


Riferimenti:

http://www.javabeat.net/2011/11/writing-an-osgi-web-application/

http://www.osgi.org/Technology/WhatIsOSGi
http://www.osgi.org/Technology/HowOSGi
http://it.wikipedia.org/wiki/OSGi
http://www.vogella.com/articles/OSGi/article.html

https://docs.jboss.org/author/display/AS7/Helloworld+OSGi+quickstart
http://docs.jboss.org/author/display/JBOSGI/Developer+Documentation
http://jbossosgi.blogspot.it/2010/11/jboss-as7-osgi-integration.html
http://www.knopflerfish.org/osgi_service_tutorial.html
http://felix.apache.org/site/index.html

sabato 15 settembre 2012

Tutorial OSGI: collaborazione tra 2 bundle su JBoss 7.1 - parte II


Tutorial OSGI: collaborazione tra 2 bundle su JBoss 7.1 - parte II

Tutorial OSGI - parte II
Collaborazione tra 2 bundle su JBoss 7.1.1

Nella prima parte del tutorial abbiamo introdotto le specifiche OSGI e abbiamo iniziato a vedere il codice di esempio per ottenere un semplice memory monitor per il controllo periodico dello stato di utilizzo della memoria sul nostro application server e in grado di mandare all’occorrenza delle mail di allarme.

Abbiamo già visto i dettagli del BundleMailSender, cioè il modulo per inviare le mail, adesso ci occuperemo del BundleMemoryMonitor.

Il BundleActivator

Partiamo dalla classe BundleActivator che dovrebbe a questo punto essere di facile comprensione:

package it.lcianci.test.osgi;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class MemoryMonitorActivator implements BundleActivator {

private static BundleContext context;
private MemoryMonitor memo;

static BundleContext getContext() {
return context;
}

public void start(BundleContext bundleContext) throws Exception {
MemoryMonitorActivator.context = bundleContext;
memo = new MemoryMonitor(5, 50, bundleContext);
}

public void stop(BundleContext bundleContext) throws Exception {
memo.stopMemoryMonitor();
MemoryMonitorActivator.context = null;
}
}

Come si può vedere, all’interno del metodo start, istanziamo un oggetto di tipo MemoryMonitor e passiamo come parametro il nostro Bundle Context, capiremo più avanti il perchè.

Il file Manifest

Veniamo adesso al file MANIFEST.MF ed al suo contenuto:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: BundleMemoryMonitor
Bundle-SymbolicName: BundleMemoryMonitor;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: it.lcianci.test.osgi.MemoryMonitorActivator
Import-Package: it.lcianci.test.osgi.util,org.osgi.framework;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-Vendor: Luca Cianci
Require-Bundle: BundleMailSender;bundle-version="1.0.0"

Analizziamo i punti salienti in grassetto:

  • Bundle-SymbolicName presenta un ulteriore attributo (singleton:=true) che mi permette di definire la mia classe come singleton ed evitare quindi di istanziarne più di una copia a seguito di differenti operazioni di start/stop.
  • Import-Package contiene in questo caso anche l’import del mio package presente nel BundleMailSender ed è possibile perchè, se vi ricordate, all’interno dell’altro file manifest avevamo una direttiva di tipo Export-Package. In questo modo possiamo usare dentro il MemoryMonitor la classe MailSender.
  • Require-Bundle specifica la dipendenza ed il legame tra i due bundle. In pratica il deploy del BundleMemoryMonitor non verrà “risolto” se sul server non è presente il BundleMailSender.

>> Nota: Nella prima parte del tutorial non abbiamo parlato in dettaglio degli stati relativi al ciclo di vita di un bundle. Questa immagine può servirci per comprendere il senso della “risoluzione” del deploy, ovvero la fase in cui viene verificata la presenza di tutti i vincoli e le dipendenze.


Dallo stato resolved si passa, in caso di successo, allo stato started e quindi active, stato in cui  il nostro bundle è pronto per essere utilizzato.

Il Memory Monitor

Vediamo adesso il codice relativo al MemoryMonitor vero e proprio.
Inserisco volutamente per esteso l’intero codice che fa uso dell’API JMX per il check sulla memoria. Penso possa essere interessante come argomento a contorno.

package it.lcianci.test.osgi;

import it.lcianci.test.osgi.util.MailSender;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.ArrayList;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

public class MemoryMonitor implements Runnable {

   final double UN_MEGA = 0.000000954;
   final int UN_SECONDO = 1000;
   private BundleContext bundleContext;
   private boolean active;
   public int checkTimeout;
   public int checkMemory;
   public MemoryMXBean memBean;

   public MemoryMonitor(int timeout, int memory,BundleContext bc) {
       checkTimeout  = timeout;
       checkMemory   = memory;
       active        = true;
       bundleContext = bc;
       
       new Thread(this).start();
   }

   @SuppressWarnings("unchecked")
   public void run() {
       try {
        double usedMem, maxMem;

             while(isRunning()) {
             Thread.sleep(checkTimeout * UN_SECONDO);
               memBean = ManagementFactory.getMemoryMXBean();
               usedMem = Math.rint(memBean.getHeapMemoryUsage().getUsed() * UN_MEGA);
               maxMem  = Math.rint(memBean.getHeapMemoryUsage().getMax() * UN_MEGA);
               System.out.println(this.getClass().getSimpleName()+" : "+usedMem+"/"+maxMem+" Mb");
               
               if (usedMem > checkMemory) {
                System.out.println("Soglia di memoria critica!");
               
                ServiceReference sr = bundleContext.getServiceReference(MailSender.class.getName());
                MailSender ms = (MailSender)bundleContext.getService(sr);
                    ArrayList dest = new ArrayList();
                    dest.add("luca.cianci@gmail.com");
                    ms.sendMail("OSGIMemoryMonitor", "Memoria critica", "Soglia di memoria superata", dest);                               
               }
           }
       }
       catch(Exception e) {e.printStackTrace();}
   }
   
   private boolean isRunning() {
return active;
}
   
   public void stopMemoryMonitor() {
active = false;
}
}

Concentriamoci solo sul codice in grassetto:

  1. ServiceReference sr = bundleContext.getServiceReference(MailSender.class.getName());
  2. MailSender ms = (MailSender)bundleContext.getService(sr);
  3. ArrayList dest = new ArrayList();
  4. dest.add("luca.cianci@bummolo.blogspot.com");
  5. ms.sendMail("OSGIMemoryMonitor","Memoria critica","Soglia di memoria superata",dest);


  • Riga 1 - usiamo il Bundle Context per recuperare un oggetto ServiceReference relativo al nostro MailSender. Questo è possibile perchè nel BundleMailSender avevamo registrato il servizio all’interno del BundleContext, ricordate?
  • Riga 2 - recuperiamo il servizio dal Bundle Context usando il ServiceReference per effettuare la lookup sul registry dei servizi.
  • Riga 5 - invochiamo il metodo sendMail sull’oggetto MailSender recuperato dal Bundle Context attraverso una lookup nel registry ed esposto come servizio.

Questo schema può chiarire meglio la divisione logica dei layer OSGI così da comprendere l’intero processo:



  • Generiamo i moduli  e li carichiamo tramite il layer Modules,
  • Gestiamo i moduli tramite il layer Life Cycle,
  • Esponiamo dei servizi tramite il layer Service Registry,
  • Usiamo i servizi esposti tramite il layer Services.

Una volta completato il nostro bundle non ci resta che esportarlo in formato jar. Come già detto in precedenza, se avete usato il wizard dei plugin di Eclipse usate “export, deployable plugin and fragment” altrimenti esportate il jar normalmente avendo cura però di specificare il nostro file manifest appena creato.
Una volta esportato il bundle possiamo effettuarne il deploy su JBoss mediante la console diamministrazione alla porta 9990 dalla sezione “Deployments/Manage Deployments” e poi avviarla. Fatto questo, sempre dalla console di amministrazione, spostandoci sulla sezione “Runtime Operations/OSGI” troverete in elenco il nostro Bundle in stato attivo.Poichè questo bundle è di fatto un Thread, se guardate lo standard output o il file di log scoprirete che ogni 5 secondi viene scritto un messaggio simile al seguente nel caso in cui il valore di soglia di 50Mb venga superato:11:44:22,132 INFO (Thread-80) MemoryMonitor : 69.0/455.0 Mb
11:44:22,134 INFO (Thread-80) Soglia di memoria critica!
11:44:22,136 INFO (Thread-80) Mail di allarme a luca.cianci@bummolo.blogspot..com

Direi che questo termina il nostro tutorial di esempio.

Ho voluto trattare la collaborazione tra differenti bundle perchè mi sembrava uno degli elementi di base per iniziare a comprendere l’interazione tra moduli e al tempo stesso il disaccoppiamento che si viene a creare tra gli stessi. Questo disaccoppiamento ci consente di trattare tutti i moduli separatamente e li rende manutenibili in maniera isolata gli uni dagli altri.

Riferimenti:

La prima parte di questo tutorial
http://www.osgi.org/Technology/WhatIsOSGihttp://www.osgi.org/Technology/HowOSGihttp://it.wikipedia.org/wiki/OSGihttp://www.vogella.com/articles/OSGi/article.htmlhttp://www.javabeat.net/2011/11/writing-an-osgi-webapplication/https://docs.jboss.org/author/display/AS7/Helloworld+OSGi+quickstarthttp://docs.jboss.org/author/display/JBOSGI/Developer+Documentationhttp://jbossosgi.blogspot.it/2010/11/jboss-as7-osgi-integration.htmlhttp://www.knopflerfish.org/osgi_service_tutorial.htmlhttp://felix.apache.org/site/index.htmlLe immagini sono prese in prestito dal sito OSGI Alliance.

giovedì 13 settembre 2012

Tutorial OSGI: collaborazione tra 2 bundle su JBoss 7.1 - parte I

Tutorial OSGI - parte I
Collaborazione tra 2 bundle su JBoss 7.1.1

In questo articolo parleremo delle specifiche OSGI e di come sviluppare applicazioni modulari,  dette “bundle”, attinenti a tali specifiche. In particolare svilupperemo un semplice progetto di esempio per illustrare il meccanismo di collaborazione tra differenti moduli.

Concetti di base

Per quelli completamente a digiuno sull’argomento, diciamo soltanto che le specifiche OSGI definiscono un modello standard e modulare per il ciclo di vita del software mettendo a disposizione un ambiente comune per consentire la cooperazione tra i vari bundle.
Grande attenzione viene data alla risoluzione delle dipendenze, che avviene in maniera dinamica e in modo tale che un bundle non venga nemmeno installato se tutti i moduli correlati non sono presenti e disponibili. Sfruttando le potenzialità messe a disposizione per il ciclo di vita del modulo è possibile installare, avviare, stoppare ed aggiornare un determinato modulo senza la necessità di effettuare alcun riavvio.

In rete e tra i link di riferimento in basso trovate abbastanza materiale per poter approfondire l’argomento.

Vediamo adesso di fissare alcuni concetti che ci torneranno utili nella nostra discussione:

  • Un bundle è un applicazione attinente alle specifiche OSGI installato all’interno di un application server che espone e supporta un framework OSGI. Per i miei esempi ho usato JBoss 7 all’interno del quale troviamo Apache Felix. Oltre a Felix esistono altre 3 implementazioni delle specifiche OSGI: Equinox, Knoplerfish e Spagic3.
  • Un bundle può esporre dei servizi e dei package per mettere a disposizione alcune delle sue funzioni all’ambiente di cooperazione OSGI. In pratica un bundle può utilizzare i servizi offerti da un altro bundle installato sullo stesso application server.
  • La definizione del bundle avviene aggiungendo alcuni metadati all’interno del file MANIFEST.MF indispensabile per questo tipo di progetti.
  • Se implementiamo all’interno del bundle una classe di tipo BundleActivator, tramite i metodi di start e stop potremo definire il comportamento del nostro modulo durante la fase di avvio.

Esempio pratico

Veniamo quindi ad un esempio pratico che ci permetterà di capire un pò più a fondo le dinamiche OSGI.

Implementeremo un semplice memory monitor per controllare periodicamente lo stato di utilizzo della memoria da parte del nostro application server. Se la memoria in uso supera un valore di soglia predefinito ipotizziamo di spedire una mail di alert.

Svilupperemo due moduli bundle separati:

  • BundleMailSender, espone l’interfaccia “sendMail” che consente la spedizione delle mail mediante il server di posta.
  • BundleMemoryMonitor, si occupa del lavoro effettivo di controllo della memoria e se necessario sfrutta il servizio sendMail offerto dal precedente bundle per inviare la mail di allarme.

>> Nota1: Questa divisione logica e modulare non introduce niente di nuovo ovvero anche senza utilizzare le specifiche OSGI potremmo realizzare due librerie, distribuite come archivi jar e utilizzate da un progetto più grande di tipo web o enterprise. Il valore aggiunto dei moduli bundle OSGI sta nella gestione del ciclo di vita che ci consente “a caldo” di aggiornare i singoli moduli differentemente da quanto succederebbe con i file jar per i quali dovremmo forse riavviare il server o probabilmente effettuare nuovamente il deploy dell’intera applicazione che li utilizza.

>> Nota2: Per realizzare i progetti bundle ho utilizzato il wizard di base di Eclipse relativo ai “Plugin project”. In alternativa un semplice progetto java contenente la cartella META-INF con il file Manifest ed esportato come jar funziona ugualmente bene ma all’aumentare della complessità risulta sicuramente comodo usare un’ambiente assistito. Altre possibilità sono date dai vari plugin maven realizzati ad hoc, ad esempio per il nostro caso specifico si potrebbe usare il plugin maven per Felix.

Il BundleMailSender

Veniamo quindi al primo dei 2 moduli, il MailSender.
Il progetto presenta:
  • la cartella META-INF contenente il nostro file MANIFEST.MF.
  • la cartella src all’interno della quale andremo a mettere il nostro codice.
  • la cartella lib, creata appositamente, contenente il file mail.jar con le librerie Java Mail appositamente scaricate.

Concentriamoci adesso sul codice da implementare.
Abbiamo detto che il nostro BundleMailSender deve esporre un’interfaccia all’esterno per fornire un metodo per l’invio della mail.
Le best practice di java ci dicono che la cosa più sensata è quella di creare un’interfaccia ad hoc che implementeremo successivamente con una classe concreta che rappresenta il servizio effettivo.

package it.lcianci.test.osgi.util;

import java.util.List;

public interface MailSender {
public void sendMail(String sender, String subject, String message, List recipients);

}

Occupiamoci adesso della classe concreta che implementa la nostra interfaccia:

package it.lcianci.test.osgi.util;

import java.util.List;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class MailSenderImpl implements MailSender {
@Override
public void sendMail(String sender, String subject, String message, List recipients) {
// Codice per invio mail

System.out.println("Mail di allarme a "+recipients.get(0).toString());
}
}

Il codice Java Mail esula dallo scopo di questo articolo e rendeva il tutto più complicato da leggere quindi l’ho volutamente eliminato.

A questo punto, dato che vogliamo registrare il servizio “sendMail” all’interno dell’ambiente di collaborazione dei bundle OSGI, ci serve implementare un BundleActivator così come previsto dalle specifiche OSGI.
L’interfaccia BundleActivator espone i due metodi start e stop. Questi metodi vengono invocati all’occorrenza dall’application server.

package it.lcianci.test.osgi.util;

import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class MailSenderActivator implements BundleActivator {

private static BundleContext context;

static BundleContext getContext() {
return context;
}

public void start(BundleContext bundleContext) throws Exception {
MailSenderActivator.context = bundleContext;
MailSender ms = new MailSenderImpl();
Hashtable props = new Hashtable();
props.put("nome","MailSender");
props.put("descrizione","Il mio bel MailSender");
bundleContext.registerService(MailSender.class.getName(), ms, props);
}

public void stop(BundleContext bundleContext) throws Exception {
MailSenderActivator.context = null;
}
}
 
All’interno dei due metodi abbiamo la possibilità di interagire con il BundleContext che rappresenta appunto l’ambiente di collaborazione condiviso tra i vari bundle e gestito per noi dal framework OSGI.
All’interno del metodo start andiamo quindi a registrare con “registerService” una classe di tipo MailSender avendo anche la possibilità di aggiungere delle meta informazioni a corredo all’interno di un HashMap di proprietà specifiche.

Resta soltanto da definire correttamente il file manifest che nel nostro caso conterrà le seguenti informazioni:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: BundleMailSender
Bundle-SymbolicName: BundleMailSender
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: it.lcianci.test.osgi.util.MailSenderActivator
Bundle-Vendor: Luca Cianci
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-ClassPath: lib/,src/,.
Export-Package: it.lcianci.test.osgi.util;uses:="org.osgi.framework"

A parte le informazioni di servizio come nome, autore e altro, risultano particolarmente importanti le proprietà in grassetto:
  • Bundle-SymbolicName e Bundle-Version sono importanti perchè ci permettono di ricavare il nome logico da utilizzare per recuperare un bundle da codice, se necessario, usando questa sintassi name:version (quindi useremmo “BundleMailSender:1.0.0”).
  • Bundle-Activator serve per dire al framework OSGI quale classe utilizzare in fase di start/stop, il nostro BundleActivator appunto.
  • Export-Package ci serve per fare in modo che il servizio esposto sia utilizzabile anche dagli altri Bundle.

A questo punto non resta che esportare il progetto in formato jar.
Se avete usato il wizard dei plugin di Eclipse usate “export, deployable plugin and fragment” altrimenti esportate il jar normalmente avendo cura però di specificare il nostro file manifest appena creato.
Una volta esportato il bundle possiamo effettuarne il deploy su JBoss mediante la console di amministrazione alla porta 9990 dalla sezione “Deployments/Manage Deployments” e poi avviarla. Fatto questo, sempre dalla console di amministrazione, spostandoci sulla sezione “Runtime Operations/OSGI” troverete in elenco il nostro Bundle in stato attivo.

Per il momento mi fermo. Nella seconda parte del tutorial vedremo la parte relativa al BundleMemory Monitor.

Riferimenti:

http://www.osgi.org/Technology/WhatIsOSGi
http://www.osgi.org/Technology/HowOSGi
http://it.wikipedia.org/wiki/OSGi
http://www.vogella.com/articles/OSGi/article.html
http://www.javabeat.net/2011/11/writing-an-osgi-web-application/
https://docs.jboss.org/author/display/AS7/Helloworld+OSGi+quickstart