La
classe ListJobs è 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.
|