Introduzione
In questa seconda parte, tratteremo il cuore del mailing-list, e quindi per come è stato progettato, parleremo della classe ListJobs. Questa classe è instanziata dal costruttore del MailingListServer, e vengono passati come parametri un riferimento alla classe Timer e uno alla classe SynchronizationMonitor. La prima ci servirà per la gestione della scheduledJobs, la seconda è una semplice classe che servirà per sincronizzare i threads quando si inserisce in currentJobs e poi quando si è in attesa di nuovi Jobs da elaborare. Ecco la SynchronizationMonitor:
class SynchronizationMonitor {
private boolean available = false;
public synchronized void get() {
if (available == false) {
try { wait(); }
catch (InterruptedException e) { System.out.println("InterruptedException"); }
}
available = false;
}
public synchronized void put() {
if (available == true) {
return;
}
available = true;
notify();
}
}
ListJobs include due strutture principali: una contiene i Jobs da inviare immediatamente (currentJobs), un'altra contiene quelli da inviare dopo una certa data prefissata (scheduledJobs). Occorre quindi dichiararle:
public SortedSet currentJobs = Collections.synchronizedSortedSet(new TreeSet(new JobsComparator())); public Vector scheduledJobs = new Vector();
La prima consente di inserire Jobs sincronizzandoli e confrontadoli in base alle priorità (in JobsComparator). La seconda è un semplice Vector. La JobsComparator è un classe che estenderà un Comparator e la metodologia di ordinamento sarà la priorità del Job.
Nel costruttore di questa classe, andremo a deserializzare il file che contiene lo stato del mailing-list server. La scelta di questa tecnica è dovuta al fatto che nel caso di crash del sistema, viene salvato (deserializzato) l'ultimo invio della mailing-list su file system. In questo modo, se facciamo ripartire il mailing-list server, si attiveranno i vari timer e il sistema ripartirà dall'ultimo invio effettuato.
I metodi per serializzare/deserializzare le strutture sono i seguenti:
// Metodo per la serializzazione
public synchronized void serialize() {
try {
FileOutputStream out = new FileOutputStream(JOBS_LISTS);
ObjectOutput s = new ObjectOutputStream(out);
s.writeObject(currentJobs);
s.writeObject(scheduledJobs);
s.flush();
s.close();
out.close();
} catch (Exception e) {
logger.fatal("Exception: nella serializzazione delle liste", e);
System.exit(1);
}
}
// Metodo per la deserializzazione
public synchronized void deserialize() {
try {
FileInputStream in = new FileInputStream(JOBS_LISTS);
ObjectInputStream s = new ObjectInputStream(in);
currentJobs = (SortedSet)s.readObject();
scheduledJobs = (Vector)s.readObject();
s.close();
in.close();
} catch (Exception e) {
logger.fatal("Exception: nella deserializzazione delle liste", e);
System.exit(1);
}
}
La costante JOBS_LISTS indica il file da serializzare, ad esempio 'conf/jobLists.ser'. Il logger appartiene al package org.apache.log4j e viene utilizzato per settare i log del sistema. Da come si può facilmente notare, andremo a memorizzare su file system le strutture descritte precedentemente.
Dopo aver recuperato gli oggetti che contengono i Jobs, si verifica, sempre nella ListJobs, se ci sono dati; nel caso particolare dello scheduledJobs, andremo a recuperare tutti i Jobs dal file JOBS_LISTS e, per ognuno di essi, si confronta la data in cui inviare con la data di sistema; se la prima è maggiore della seconda, lanceremo il comando:
timer.schedule(new JobScheduleDaemon(job, this, sm), job.getBeginDate());
Questo comando è lo stesso che troveremo ovviamente nel MailingListServer ed in particolare nel metodo send che contiene appunto la data di invio. Qui chiameremo il metodo schedule della classe Timer con i parametri TimerTask e la data di invio. JobScheduleDaemon estende perciò un TimerTask; il costruttore mantiene i riferimenti del job, della classe ListJobs e della classe SynchronizationMonitor. Il cuore di JobScheduleDaemon è il metodo run() che viene eseguito dal TimerTask quando la data passata al Timer è attiva. In questo metodo, sposteremo il Job da currentJobs a scheduledJobs, notificheremo al thread di inserimento tramite flag che un Job deve essere elaborato e quindi eseguiamo il comando sm.put().
Se la data di invio è < della data di sistema chiameremo il metodo
moveFromScheduleToCurrent(job);
e cioè:
// Sposta il Job passato come parametro dal vettore scheduledJobs
// alla collection currentJobs.
public void moveFromScheduleToCurrent(Job job) {
scheduledJobs.remove(job);
currentJobs.add(job);
serialize();
}
L'ultima classe da esaminare in questo articolo è JobExtractor. Quest'ultima è un thread in ascolto su currentJobs. Il thread appena riceve una put della SynchronizationMonitor va a controllare la dimensione della lista solo se il MailSender è alive. Se non è alive, va ad estrarre il primo Job, che è quello con priorità più alta, lo cancella e da currentJobs, serializza le strutture di ListJobs e invia il Job alla MailSender.
Conclusione
La descrizione della classe ListJobs è effettivamente complessa, ma sarà più facile comprenderla se abbiamo in mente la struttura creata nell'articolo del mese precedente. Occorre focalizzare bene il funzionamento della ListJobs, che è in effetti il cuore del sistema. In particolare sulla classe SynchronizationMonitor che permette di sincronizzare i vari thread che "girano" nell'applicazione. Ricapitolando, i threads sono i seguenti:
- JobExtractor: che controlla la struttura currentJobs; appena c'è una put da parte o di uno dei due metodi send della MailingListServer oppure da JobScheduleDaemon che sposta un Job da scheduledJobs a currentJobs, vuol dire che c'è da elaborare qualcosa.
- JobScheduleDaemon: che si attiva quando una data è maggiore di quella di sistema.
In seguito ci sarà (dal prossimo e ultimo articolo) anche il thread MailSender.
