Jpathwatch: una libreria Java per il monitoring di directory e di file

Jpathwatch: una libreria Java per il monitoring di directory e di file

In questo articolo analizzeremo una libreria Java per il monitoring di directory ed il file changing, in altre parole questa libreria permette di essere avvisati nel momento in cui un file o più file presenti in una directory o una sottodirectory vengono modificati, creati o cancellati. Il funzionamento alla base di questa libreria è quello di rimanere in polling, il cui intervallo può essere cambiato, su di una determinata cartella finchè un dato evento non viene lanciato. In alternativa, per evitare il polling, vengono utilizzate le funzionalità native del Sistema Operativo dell'host su cui vengono utilizzate.

TagSoup: un parser HTML per Java
Creare un file eseguibile .exe per le applicazioni Java con Launch4j
Java Decompiler: un decompilatore per Java

In questo articolo analizzeremo una libreria Java per il monitoring di directory ed il file changing, in altre parole questa libreria permette di essere avvisati nel momento in cui un file o più file presenti in una directory o una sottodirectory vengono modificati, creati o cancellati.

Il funzionamento alla base di questa libreria è quello di rimanere in polling, il cui intervallo può essere cambiato, su di una determinata cartella finchè un dato evento non viene lanciato. In alternativa, per evitare il polling, vengono utilizzate  le funzionalità native del Sistema Operativo dell’host su cui vengono utilizzate.

E’ bene ricordare che a partire da Java 7 sono state introdotte delle funzionalità di monitoring dei file con la specifica JSR 203:

http://jcp.org/en/jsr/detail?id=203

in particolare tramite la classe WatchService (http://docs.oracle.com/javase/tutorial/essential/io/notification.html), le funzionalità offerte sono però  limitate e vengono supportati solo i Sistemi Operativi Windows e Linux.

Jpathwatch invece implementa le API WatchService, le arricchisce di funzionalità e funziona su numerose piattaforme.

Attualmente vengono supportate nativamente le seguenti piattaforme:

  • Windows (Windows 2000, XP, Vista, 7, 32bit/64bit);
  • Linux (x86, 32bit/64bit);
  • FreeBSD (x86, 32bit);
  • Mac OS X
    • (x86, 32bit/64bit, testato su  10.5);
    • (PPC, testato su 10.4).

Gli eventi che possono essere monitorati su una data directory sono i seguenti:

  • la creazione e la cancellazione di un file;
  • modifica di un file;
  • rinomina di un file*;
  • cambiamenti nelle sottodirectory (ricorsivo)*;
  • invalidazione (una directory sotto osservazione viene invalidata).
Gli eventi contrassegnati dall’asterisco (*) sono supportati solo nelle seguenti piattaforme:
Tipo di eventoDescriz.WinLinuxMac

OSX

FreeBSDAltri
StandardWatchEventKind

.ENTRY_CREATE

file creatoSiSiSiSiSi*
StandardWatchEventKind

.ENTRY_DELETE

file cancellatoSiSiSiSiSi*
StandardWatchEventKind

.ENTRY_MODIFY

file modificatoSiSiSi*Si*Si*
ExtendedWatchEventKind

.ENTRY_RENAME_FROM

file rinominato (vecchio nome)SiSiNoNoNo
ExtendedWatchEventKind

.ENTRY_RENAME_TO

file rinominato (nuovo nome)SiSiNoNoNo
ExtendedWatchEventKind

.KEY_INVALID

key invalidata (da un utente o un evento esterno)SiSiSi*Si*Si*
Tabella 1
in questo caso l’asterisco (*) sta ad indicare che Jpathwatch rimarrà in polling poichè le funzionalità non sono supportate dal S.O..
Analizziamo ora alcune delle Classi e dei metodi offerti dalla libreria.
La prima classe che analizziamo è la classe Bootstrapper con la quale è possibile configurare alcuni parametri dell’intera libreria, ad esempio possiamo forzare l’uso del polling con il metodo setForcePollingEnabled(true):
Bootstrapper.setForcePollingEnabled(true);
Usiamo il metodo getDefaultPollingInterval() per poter conoscere l’intervallo di polling in millisecondi:
Bootstrapper.getDefaultPollingInterval();
ed eventualmente lo possiamo impostare con il metodo:
Bootstrapper.setDefaultPollingInterval(10000);
ad un intervallo di 10000 millisecondi.
La prima cosa da fare è creare un WatchService, lo possiamo fare in due modi:
  • con la classe Bootstrapper:
WatchService watchService = Bootstrapper.newWatchService();
  • con la classe FileSystems:
WatchService watchService = FileSystems.getDefault().newWatchService();
Fatto ciò dobbiamo creare un oggetto di tipo Path che ingloba la directory da monitorare:
</pre>
</div>
<div>Path watchedPath = Bootstrapper.newPath(new File("Stringa del path da monitorare"));</div>
<div>
Infine per poter monitorare effettivamente questo Path bisogna registrarlo presso un WatchService, ad esempio quello che abbiamo creato in precedenza, utilizzando il metodo register:
WatchKey key = null;
try {
key = watchedPath.register(watchService, StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_DELETE,StandardWatchEventKind.ENTRY_MODIFY);
} catch (UnsupportedOperationException uox){
System.err.println("Evento di File watching non supportato!");
}catch (IOException iox){
System.err.println("I/O errors");
}
al metodo register vanno passati gli eventi per i quali si vuole essere notificati, gli eventi supportati sono quelli indicati nellaTabella 1.
A questo punto non ci resta che lanciare il metodo take() sulla WatchKey ritornata dal metodo register(), il quale resta in attesa che qualche evento accada sulla cartella monitorata; lasciamo che il metodo take() venga richiamato dopo ogni evento racchiudendolo in un ciclo infiinito come segue:
for(;;){
// take() blocca finchè un file non viene creato, modificato o cancellato
WatchKey signalledKey=null;
try {
signalledKey = watchService.take();
} catch (InterruptedException ix){
// ignoriamo l eccezione se qualche altro thread ha chiamato il metodo interrupt sul watch service
continue;
} catch (ClosedWatchServiceException cwse){
// un altro thread ha chiuso il watch service
System.out.println("watch service closed, terminating.");
break;
}
// otteniamo la lista di eventi dall'oggetto key
List> list = signalledKey.pollEvents();
// resettiamo la lista di eventi dall'oggetto key
signalledKey.reset();
// qui semplicemente stampiamo quello che è successo nella directory monitorata
for(WatchEvent e : list){
String message = "";
if(e.kind() == StandardWatchEventKind.ENTRY_CREATE){
Path context = (Path)e.context();
message = context.toString() + " creato";
} else if(e.kind() == StandardWatchEventKind.ENTRY_DELETE){
Path context = (Path)e.context();
message = context.toString() + " cancellato";
} else if(e.kind() == StandardWatchEventKind.ENTRY_MODIFY){
Path context = (Path)e.context();
message = context.toString() + " modificato";
} else if(e.kind() == ExtendedWatchEventKind.ENTRY_RENAME_FROM){
Path context = (Path)e.context();
message = context.toString() + " ridenominato";
}else if(e.kind() == StandardWatchEventKind.OVERFLOW){
message = "OVERFLOW: sono accaduti più cambiamenti di quanti era possibile rilevare";
}
System.out.println(message);
}
}
il metodo take() non termina finché non c’è un evento che abbiamo registrato presso il WatchService o fino a quando questi viene chiuso con il metodo close(), dopodiché otteniamo la lista degli eventi occorsi con il metodo poolEvents(), iteriamo su questa lista e stampiamo il tipo di evento occorso.
Vediamo ora come utilizzare questa libreria all’interno di un nostro progetto Java.
La prima cosa da fare è scaricare l’ultima versione della libreria dalla seguente URL:
Per i nostri esempi utilizziamo Eclipse: creiamo un nuovo Progetto Java (File -> New -> Java Project), creiamo una cartella lib e vi copiamo la libreria jpathwatch-version.jar, non dimentichiamoci poi di includerla nel Build Path del nostro progetto: click col tasto destro -> Build Path -> Configure Build Path):

Build path della nostra applicazione

Creiamo poi la seguente classe all’interno del package it.appuntisoftware:
TestJPathWatch.java
package it.appuntisoftware;
import java.io.File;
import java.io.IOException;
import java.util.List;
import name.pachler.nio.file.*;
import name.pachler.nio.file.ext.Bootstrapper;
import name.pachler.nio.file.ext.ExtendedWatchEventKind;
public class TestJPathWatch {
public static void main (String args[]){
 new TestJPathWatch().test();
}
public void test(){
 long mill = Bootstrapper.getDefaultPollingInterval();
 System.out.println("Default Polling Interval: "+mill );
 Bootstrapper.setDefaultPollingInterval(10000);
 boolean bool = Bootstrapper.isForcePollingEnabled();
 if(!bool)
  Bootstrapper.setForcePollingEnabled(true);
 mill = Bootstrapper.getDefaultPollingInterval();
 System.out.println("Set Polling Interval: "+mill );
 WatchService watchService = Bootstrapper.newWatchService();
 Path watchedPath = Bootstrapper.newPath(new File("C:\Documents and Settings\Angelo\Documenti"));
 WatchKey key = null;
 try {
  key = watchedPath.register(watchService, StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_DELETE,StandardWatch  EventKind.ENTRY_MODIFY);
  } catch (UnsupportedOperationException uox){
   System.err.println("Evento di File watching non supportato!");
  }catch (IOException iox){
   System.err.println("I/O errors");
  }
 for(;;){
 // il metodo take() blocca il thread finchè un file non viene creato, modificato o cancellato
 WatchKey signalledKey=null;
 try {
  signalledKey = watchService.take();
 } catch (InterruptedException ix){
  // ignoriamo l eccezione se qualche altro thread ha chiamato il metodo interrupt sul watch service
  continue;
 } catch (ClosedWatchServiceException cwse){
  // un altro thread ha chiuso il watch service
  System.out.println("watch service closed, terminating.");
  break;
 }
 // otteniamo la lista di eventi dall'oggetto key
 List> list = signalledKey.pollEvents();
 // resettiamo la lista di eventi dall'oggetto key
 signalledKey.reset();
 // qui semplicemente stampiamo quello che è successo nella directory monitorata
 for(WatchEvent e : list){
  String message = "";
  if(e.kind() == StandardWatchEventKind.ENTRY_CREATE){
  Path context = (Path)e.context();
  message = context.toString() + " creato";
  } else if(e.kind() == StandardWatchEventKind.ENTRY_DELETE){
   Path context = (Path)e.context();
   message = context.toString() + " cancellato";
  } else if(e.kind() == StandardWatchEventKind.ENTRY_MODIFY){
   Path context = (Path)e.context();
   message = context.toString() + " modificato";
  } else if(e.kind() == ExtendedWatchEventKind.ENTRY_RENAME_FROM){
   Path context = (Path)e.context();
   message = context.toString() + " ridenominato";
  }else if(e.kind() == StandardWatchEventKind.OVERFLOW){
   message = "OVERFLOW: sono accaduti più cambiamenti di quanti era possibile rilevare";
  }
  System.out.println(message);
  }
 }
}
}

Naturalmente il thread lanciato dal metodo main (il thread principale del nostro programma) si blocca alla chiamata del metodo take(), di conseguenza è preferibile creare un thread secondario da cui  lanciare il metodo test() .

COMMENTS

WORDPRESS: 0