MokaByte 72- Marzo 2003 
EJB 2.1
Il Timer Service e Timer Beans
di
Giovanni Puliti
L'articolo verrà pubblicato a breve

Introduzione
All'interno dello scenario J2EE, ed in particolare in EJB è sempre crescente l'interesse nel gestire meccanismi di schedulazione automatica in grado di lavorare direttamente con enterprise beans della applicazione.
Già alcuni prodotti sono attualmente disponibili nella piattaforma J2EE come Flux di Sims Computing, Web Logic Time Service di Bea, ma prima della specifica EJB 2.1 nessuno di questi prodotti e tecnologie è stata ufficialmente introdotta nella specifica ufficiale.
Nella specifica 2.1 è stato introdotto un supporto seppur limitato per questo tipo di servizio.
In realtà già in J2SE era stato introdotta la classe java.util.Timer, che consente la schedulazione temporale di thread paralleli, anche se ovviamente è del tutto insufficiente in ottica J2EE.
Il Timer Service è un nuovo servizio aggiunto al container con la specifica 2.1: esso consente di attivare timer per determinati periodi di tempo o allo scadere determinate date.
Al momento in cui si scrive questo articolo la specifica non è ancora stata rilasciata ufficialmente e quindi non è possibile testare quanto qui descritto su un application server particolare. Per questo motivo gli argomenti qui trattati potranno differire dalla versione definitiva e quindi è compito del lettore verificarne la validità quando i vari prodotti commerciali e non, implementeranno tale specifica.
Il TimerService Interface
Questo nuovo servizio inserito all'interno del container consente ad un enterprise bean di essere notificato quando un determinata data scade o al trascorrere di un determinato intervallo di tempo. Per utilizzare il servizio un enterprise bean deve implementare l'interfaccia javax.ejb.TimeObject che definisce un solo metodo di callback ejbTimeout()

public interface TimeObject{
  public void ejbTimeout(Timer timer)
}

Quando il tempo timer scade oppure una determinata data arriva, il metodo ejbTimeout() viene invocato dal container.
Per convenzione nella specifica si parla di scadenza di un timer: tale definizione intende il momento il timer viene mandato in esecuzione, o per il raggiungimento di una data o per lo scadere di un intervallo di tempo.
Per essere attivato un bean deve ricavare un reference all'oggetto TimerService direttamente dall'EJBContext: questo consente al bean di registrarsi come ascoltatore della scadenza di una data o dello scadere di un determinato periodo.
Il codice seguente mostra come ciò sia possibile

Calendar time = Calendar.getInstance();
time.add(Calendar.DATE, 60);
Date date = time.getTime();
EJBContext context = …
TimerService timer = context.getTimerService();
Timer.createTimer(date, null);

Nel codice precedente viene per prima cosa creato un oggetto Calendar, lo incrementa di 60 giorni in modo da rappresentare la scadenza del timer. Il timer vero e proprio viene ottenuto dal context EJB tramite il metodo createTimer() del Timer Service.
Tale interfaccia fornisce ai bean la possibilità di accedere al timer service in modo da creare timers con determinate scadenze fisse o con scadenze ricorrenti

package javax.ejb;
import java.util.Date;
import java.io.Serializable;
public interface TimerService {

  // Crea un single-action timer che scade ad una data specifica
  public Timer createTimer(Date expiration, Serializable info)
               throws IllegalArgumentException,
                      IllegalStateException,
                      EJBException;

  // Crea un single-action timer che scade dopo lo scorrere
  // di un determinato periodo di tempo
  public Timer createTimer(long duration, Serializable info)
               throws IllegalArgumentException,
               IllegalStateException,
               EJBException;

  // Crea un interval timer che parte ad una specifica data
  public Timer createTimer(Date initialExpiration,
                           long intervalDuration,
                           Serializable info)
               throws IllegalArgumentException,                       IllegalStateException,
                      EJBException;

  // Create un interval timer che scade dopo lo scorrere di
  // un determinato periodo di tempo
  public Timer createTimer(long initialDuration,
                           long intervalDuration,
                           Serializable info)
               throws IllegalArgumentException,
                      IllegalStateException,
                      EJBException;

  // Ricava tutti i timer attivi associati al bean
  public Collection getTimers()
                    throws IllegalStateException,
                           EJBException;

}


Ognuno di questi metodi createTimer() consente di creare timer in modo diverso, ed in particolare di creare i due tipi di timer previsti: i single-action timers ed gli interval-timers.
I primi corrispondono a timer che scadono in determinati momenti, mentre gli altri scadono in modo continuativo ogni tot millisecondi.
Un timer al momento della creazione viene reso persistente presso un qualche sistema di storage secondario: se il sistema si blocca o va in crash per qualche motivo, al riavvio il timer torna ad essere attivo nuovamente al momento. Al momento la specifica non dice in modo chiaro cosa accada se un timer scade nel momento della indisponibilità del server, lasciando alla implementazione del server l'interpretazione precisa. Probabilmente i timer dovrebbero venire attivati tante volte quante sono le date di scadenza avvenute durante il periodo di inutilizzo.
Si consiglia a tal proposito di consultare la documentazione dell'application server utilizzato.

 

Timer Interface
Un timer è un oggetto che implementa l'interfaccia javax.ejb.Timer, cosa che permette di creare session entity e mdb in grado di ricevere notifiche temporali dal Timer Service.
Un timer può essere ricavato tramite l'invocazione del metodo

TimerService.getTimer()

o

TimerService.getTimers()

Il metodo TimerObject.ejbTimeout() è quello invocato in callbak dal container ricevendo un oggetto che implementa Timer come parametro direttamente dal container. L'interfaccia Timer ha la seguente definizione

package javax.ejb;

public interface Timer {

  // Cancella il timer
  public void cancel() throws IllegalStateException,
                              NoSuchObjectLocalException,
                              EJBException;

  // Restituisce le informazioni associate al timer al
  // momento della creazione
  public Serializable getInfo() throws IllegalStateException,
                                       NoSuchObjectLocalException,
                                       EJBException;

  // Restituisce la prossima scadenza del timer
  public Date getNextTimeout() throws IllegalStateException,
                                      NoSuchObjectLocalException,
                                      EJBException;

  // Restituisce in millisecondi la prossima scadenza del timer
  public long getTimeRemaining() throws IllegalStateException,
                                        NoSuchObjectLocalException,
                                        EJBException;

  // Restituisce in forma serializzata un handle al timer
  public TimerHandle getHandle() throws IllegalStateException,
                                        NoSuchObjectLocalException,
                                        EJBException;
}

Una istanza di Timer rappresenta esattamente un evento temporale e può essere utilizzata per annullare il timer, per ricavare un handle serializzato, per ricavare i dati applicativi associati al timer, o per ricavare le informazioni temporali sulla scadenza del timer stesso.
Ad esempio il metodo getInfo() restituisce una versione serializzata del Timer tramite la quale ricavare tutte le informazioni di cui sopra.
Il metodo getHandle() invece restituisce una istanza di TimeHandle, simile alla java.ejb.Handle o java.ejb.HomeHandle: essa potrà essere salvata da qualche parte in forma serializzata e permette di ricavare il Timer in un secondo momento. La definizione di TimerHandle è la seguente

package javax.ejb;
public interface TimerHandle extends Serializable {
  public Timer getTimer() throws NoSuchObjectLocalException,
                                 EJBException;
}

Quando si invoca il metodo createTimer() l'operazione viene completata all'interno dello scope della transazione attuale (vedi [MBEJB]). In caso di una rollback l'operazione viene annullata, ovvero il timer non viene creato.

 

Entity Bean Timers
L'utilizzo di entity bean temporizzati può essere una cosa particolarmente utile, anche se di fatto introduce uno strato di business logic all'interno del dominio dei dati, per cui viola in qualche modo alcune delle best practice J2EE, secondo le quali un entity è una rappresentazione OO di dati resi persistenti da qualche parte. Se per un momento ci si dimentica di questa indicazione, l'accoppiata entity-timer offre particolari vantaggi.
Allo scadere del tempo del timer, il container ricava la chiave primaria del bean e procede al caricamento dei dati per istanziare l'entity stesso. Quando esso si trova nello stato di ready viene invocato il metodo ejbTimeout() che effettuerà le operazioni contenute al suo interno. Volendo limitare al minimo la violazione delle indicazioni date dalle best practice, si dovrà limitare l'intervento solamente ai dati propri dell'entity. In quest'ottica l'utilizzo dei timer è particolarmente utile: si tratta di strutture dati che modificano se stesse a determinati intervalli di tempo.

 

Stateless Session Beans Timers
L'utilizzo di session stateless temporizzati permette di implementare meccanismi automatici e transazionali che eseguono compiti in batch in modo semplice e veloce: ripulitura di dati nel database, trasferimento di record, monitorazione di sottosistemi particolari. Si tratta fondamentalmente di un agente transazionale che vive all'interno del container EJB, con tutti i benefici che questo ne comporta.
Differentemente dal caso precedente, dove un timer è associato ad un determinato entity, qui il timer è associato ad un particolare tipo di session. Così come l'associazione session stateless-client è generica. Quando il timer scade, una qualsiasi istanza del session viene prelevata dal pool ed eseguito il metodo ejbTimeout().
Message Driven Bean Timers
Discorso del tutto analogo può essere fatto per i message driver beans. Allo scadere del tempo, viene prelevato un MDB dal pool di quelli disponibile ed eseguito il metodo ejbTimeout().
Da ricordare che un MDB viene inizializzato al momento della ricezione del primo messaggio: molti esperti e progettisti criticavano la mancanza nella prima release della specifica EJB 2.1 della associazione MDB-Timer Service. Ora che questa lacuna è stata colmata, personalmente resto con il dubbio del reale significato di tale associazione. Un timer così come un MBD sono oggetti enterprise che sono attivati in corrispondenza di un evento esterno (il tempo o un messaggio): per questo vedo un po' promiscua la mescolanza fra i due domini di lavoro. Dato che i progettisti hanno sentito la necessità di introdurre tale funzionalità, anche in risposta di molte richieste da parte di importanti esperti del settore, è presumibile che questi miei dubbi siano non giustificati.
Inoltre manca un modo standardizzato per inizializzare in fase di deploy i MDB-Timers: di fatto essi sono inizializzati al momento della ricezione del primo messaggio, cosa che obbliga ad esempio ad inviare un messaggio automaticamente allo start della applicazione alla coda o topic su cui il MDB si è registrato. Questo, al momento, è effettivamente una limitazione molto grossa, che di fatto riduce l'utilità di questi oggetti. Se includere nella specifica la possibilità di configurare al deploy è una decisione che spetta all'EJB expert group: per il momento non resta che attendere il rilascio della release finale di EJB 2.1.

 

Problemi
L'interfaccia ed il meccanismo di creazione dei timer ha ancora molti aspetti da mettere a punto. Ad esempio, come ricordato in [EJB2.1], è alquanto difficile creare e gestire eventi ricorrenti con la stessa flessibilità offerta da Cron, il tool di amministrazione temporizzata presente su Unix. Sebbene questi argomenti esulano dalle prospettive di questo articolo, si tratta di argomenti alquanto importanti. Per maggiori approfondimenti si rimanda al link sopraccitato.

 

Conclusioni
L'introduzione del Timer Service in un container EJB è sicuramente una cosa molto importante. Il poter effettuare operazioni a determinati periodi di tempo permette di includere un altro importante servizio nelle applicazioni EJB, consentendo di eliminare applicazioni esterne (demoni o agenti) che dovrebbero essere eseguite in modalità standalone fuori dal contesto transazionale e fault tolerant tipico di EJB.
Inoltre questa modifica permette di semplificare considerevolmente il processo di configurazione di tali demoni i quali non necessiteranno di appositi sistemi server side (JVM, file di configurazione e meccanismi manutenzione) essendo il tutto contenuto in un container che unisce i vari servizi server side.
Al momento in cui scrivo questo articolo la specifica EJB 2.1 è in fase di Proposed Final Draft (Java Specification Request 153, vedi [JSR153]) e non sono note le date di rilascio della versione definitiva.

 

Bibliografia
[MBEJB] "Manuale Pratico di Java" capitolo su EJB, di Giovanni Puliti. www.mokabyte.it/mokabook o www.mokabyte.it/manualejava per la versione cartacea.
[EJB2.1] "The EJB 2.1 Specification: the Service Timer" - di Monson Haefel pubblicato su TheServerSide.com
http://www.theserverside.com/resources/articles/MonsonHaefel-Column4/article.html
[JSR153] - JSR 153 Enterprise JavaBeans 2.1 - http://jcp.org/en/jsr/detail?id=153

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it