En esta página:
Introducción
Los sistemas de negocio hacen uso frecuentemente de los sistemas de programación (en el tiempo), que pueden configurarse para ejecutar otros programas en momentos específicos. En muchos casos, los sistemas de temporización ejecutan aplicaciones que generan informes, reformatean datos, o hacen trabajos de auditoría durante la noche. En otros casos, los sistemas de temporización proporcionana APIs de retrollamada que pueden avisar a subsistemas de los eventos temporales, como fechas de vencimientos, etc.
Los sistemas de temporización frecuentemente ejecutan "trabajos por lotes" (batch), que realizan automáticamente trabajos rutinarios en un momento predeterminado. Los usuarios de UNIX ya estarán acostumbrados a ejecutar trabajos usando cron , un sencillo pero útil sistema de temporización que ejecuta los programas listados en un fichero de configuración. Otros sistemas de temporización de trabajos incluyen el COS Timer Event Service de OMG, que es un API CORBA para eventos temporizados, así como otros productos comerciales.
Los sistemas de temporización también son comunes en aplicaciones de flujo de trabajo, sistemas que manejan el procesamiento de documentos que normalmente duran días o meses, e implican muchos sistemas y cantidad de intervención humana. En este tipo de aplicaciones, se emplea la temporización para tareas de auditoria que frecuentemente hacen inventario del estado de una aplicación, facturas, ventas, pedidos, o cualquier cosa que asegure que todo va según lo programado. El sistema de temporización mantiene temporizadores, y entrega eventos para alertar a las aplicaciones y componentes cuando se alcanza una determinada fecha y hora, o cuando se ha agotado un periodo de tiempo.
En el mundo EJB, ha habido un interés general en los sistemas de temporización que puedan trabajar directamente con beans enterprise. Existen algunos productos que soportan sistemas de temporización para J2EE, como Flux® de Sims Computing y WebLogic Time Services de BEA, pero hasta EJB 2.1 no había ningún sistema de temporización estándar para J2EE. EJB 2.1 presenta un sistema de temporización estandarizado, pero limitado, que se llama Timer Service .
Nota:
La plataforma Java 2, Standard Edition incluye la clase java.util.Timer, que permite a los threads temporizar tareas para una ejecución futura en un thread de segundo plano. Esta facilidad puede ser muy útil en una gran variedad de aplicaciones, pero está demasiado limitada para usarse en programación empresarial. Observa, sin embargo, que la semántica de temporización de la clase java.util.Timer es muy similar a la utilizada por el Servicio Timer de EJB. |
Timer Service es una facilidad del sistema contenedor EJB que proporciona un API de eventos-temporizados, que se puede utilizar para programar temporizadores en fechas específicas, periodos de tiempo e intervalos. Un temporizador se asocia con un bean enterprise que lo configura, y llama al método ejbTimedout() del bean cuando se ha terminado.
El API de Timer Service
Timer Service permite que un bean enterprise sea notificado cuando se alcanza una fecha especificada, o cuando ha pasado un periodo de tiempo, o a intervalos recurrentes.
El Interface TimedObject
Para usar el Servicio Timer, un bean enterprise debe implementar el interface javax.ejb.TimedObject, que define un sólo método, ejbTimeout():
package javax.ejb; public interface TimedObject { public void ejbTimeout(Timer timer) ; }
Cuando se alcanzan una fecha y hora programadas, o ha pasado un periodo de tiempo especificado, el sistema contenedor llama al método ejbTimeout() del bean enterprise. Entonces el bean puede hacer cualquier procesamiento que necesite en respuesta a la llamada, como ejecutar informes, auditar registros, modificar estados de otros beans, etc.
El Interface TimerService
Un bean enterprise se programa a si mismo para una notificación temporal usando una referencia al TimerService, el cual obtiene del EJBContext. TimerService permite a un bean enterprise registrarse a sí mismo para una notificación en un fecha específica, o después de un periodo de tiempo, o a intervalos recurrentes. El siguiente fragmento de código muestra cómo un bean se podría registrar para que se notificara exactamente cada 30 días desde hoy:
// Create a Calandar object that represents the time 30 days from now. Calendar time = Calendar.getInstance(); // the current time. time.add(Calendar.DATE, 30); // add 30 days to the current time. Date date = time.getTime(); // Create a timer that will go off 30 days from now. EJBContext ejbContext = // ...: get EJBContext object from somewhere. TimerService timerService = ejbContext.getTimerService(); timerService.createTimer( date, null);
El ejemplo de arriba crea un objeto Calendar que representa la hora actual, luego incrementa este objeto en 30 días para que represente la fecha adecuada. Luego el código obtiene una referencia al método TimerService.createTimer() de TimerService, pasándole el valor java.util.Date del objeto Calendar, así crea un temporizador que se terminará dentro de 30 días.
El interface TimerService le proporciona a un bean enterprise acceso al Servicio Timer del contenedor de EJB's para poder crear los nuevos temporizadores y para poder listar los ya existentes. El interface TimerService forma parte del paquete javax.ejb en EJB 2.1 y tiene la siguiente definición:
package javax.ejb; import java.util.Date; import java.io.Serializable; public interface TimerService { // Create a single-action timer that expires on a specified date. public Timer createTimer(Date expiration, Serializable info) throws IllegalArgumentException,IllegalStateException,EJBException; // Create a single-action timer that expires after a specified duration. public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException,IllegalStateException,EJBException; // Create an interval timer that starts on a specified date.. public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info) throws IllegalArgumentException,IllegalStateException,EJBException; // Create an interval timer that starts after a specified durration. public Timer createTimer(long initialDuration, long intervalDuration, Serializable info) throws IllegalArgumentException,IllegalStateException,EJBException; // Get all the active timers associated with this bean public java.util.Collection getTimers() throws IllegalStateException,EJBException; }
Cada uno de los métodos TimerService.createTimer() establece un temporizador con una configuración temporal diferente. Esencialmente hay dos tipos: temporizadores de una sola acción e temporizadores de intervalo . Los primeros expiran una sola vez, y los segundos múltiples veces a intervalos especificados. El termino "expira" significa que el temporizador llega a su fin o es activado. Cuando un temporizador expira, el Servicio Timer llama al método ejbTimeout() del bean.
Cuando se crea un temporizador el Servicio Timer automáticamente lo vuelve persistente en algún tipo de almacenamiento secundario, así sobrevivirá a los fallos del sistema. Si el servidor se cae, los temporizadores seguirían activos cuando vuelva a encenderse otra vez. Aunque la especificación no esta clara, se asume que cualquier temporizador que se "expire" mientras que el servidor está apagado se "expirará" cuando el servidor se vuelva a conectar. Si un temporizador de intervalos expira varias veces mientras el servidor está apagado, cuando se encienda el servidor el temporizador se podría disparar varias veces. Debes consultar la documentación del vendedor para aprender como maneja los temporizadores ante fallos del sistema.
El método TimerService.getTimers() devuelve todos los temporizadores que se han configurado para un bean enterprise en particular. Este método devuelve un java.util.Collection, una colección desordenada de cero o más objetos javax.ejb.Timer.
El Interface Timer
Un objeto temporizador es un ejemplar de una clase que implementa el interface javax.ejb.Timer, y representa un evento temporizador que ha sido programado por un bean enterprise usando el servicio Timer. Los objetos Timer son devueltos por los métodos TimerService.createTimer() y TimerService.getTimers(), y un Timer es el único parámetro para el método TimedObject.ejbTimeout() . El interface Timer se define de esta forma:
package javax.ejb; public interface Timer { // Cause the timer and all its associated expiration notifications to be canceled public void cancel() throws IllegalStateException,NoSuchObjectLocalException,EJBException; // Get the information associated with the timer at the time of creation. public java.io.Serializable getInfo() throws IllegalStateException,NoSuchObjectLocalException,EJBException; // Get the point in time at which the next timer expiration is scheduled to occur. public java.util.Date getNextTimeout() throws IllegalStateException,NoSuchObjectLocalException,EJBException; // Get the number of milliseconds that will elapse before the next scheduled timer expiration public long getTimeRemaining() throws IllegalStateException,NoSuchObjectLocalException,EJBException; //Get a serializable handle to the timer. public TimerHandle getHandle() throws IllegalStateException,NoSuchObjectLocalException,EJBException; }
Un ejemplar de Timer represents un sólo evento temporizado que se puede utilizar para cancelar el temporizador, obtener un manejador serializable, obtener los datos de aplicación asociados con el temporizador, y encontrar cuando ocurrirá la siguiente evento temporizado.
El método Timer.getInfo() devuelve un objeto serializable; un ejemplar de una clase que implementa el interface java.io.Serializable. Este objeto serializable normalmente se le llama el objeto info , y se asocia con temporizador cuando se crea el objeto Timer. Podemos usar casi cualquier cosa en el objeto info. Debería contener información de la aplicación que ayude al bean a identificar el propósito del temporizador así como datos que puedan ayudar a su procesamiento.
El método Timer.getHandle() devuelve un objeto TimerHandle. Es objeto tiene un propósito similar a javax.ejb.Handle y javax.ejb.HomeHandle. Es una referencia que se puede grabar a un fichero o a cualquier otro recursos, luego se puede utilizar para re-obtener el acceso al objeto Timer en una fecha posterior. El interface TimerHandle es muy simple:
package javax.ejb; public interface TimerHandle extends java.io.Serializable { public Timer getTimer() throws NoSuchObjectLocalException, EJBException; }
Transacciones y Temporizadores
Cuando un bean llama a TimerService.createTimer(), la operación se hace en el ámbito de la transacción actual. Si la transacción se deshace, el temporizador también se deshace; no se crea.
En la mayoría de los beans, el método ejbTimeout() debería tener un atributo transaction de RequiresNew, para asegurarse de que el trabajo realizado por el método ejbTimeout() está en el ámbito de las transacciones iniciadas por el contenedor.
Un ejemplo de Bean Temporizador
En un sistema de compraventa de acciones, se pueden crear pedidos de compra-al-límite para un número específico de valores, pero sólo a un precio determinado o menor. Dichos pedidos normalmente tienen un tiempo límite. Si el precio de la acción cae por debajo del precio especificado antes del tiempo límite, se realiza el pedido. Si el precio de la acción no cae por debajo del precio especificado antes del tiempo límite, el temporizador se acaba y el pedido se cancela.
En una aplicación J2EE este tipo de pedidos se podría representar por un bean de entidad llamado BuyAtLimit. Cuando se crea el bean se configura un temporizador para la fecha de expiración manejado en el método create. Si se ejecuta el "pedido-al-limite", el temporizador se cancela. Y si no es así, cuando se acaba el temporizador el EJB BuyAtLimit se marcará a sí mismo como cancelado. (No borramos el pedido, porque necesitamos el registro). La definición de la clase BuyAtLimit sería similar a esta:
package com.xyzbrokers.order; import javax.ejb.*; import java.util.Collection; import java.util.Iterator; import java.util.Date; public class BuyAtLimitBean implements javax.ejb.EntityBean, javax.ejb.TimedObject { public EntityContext ejbCntxt; public void setEntityContext(EntityContext cntxt) {ejbCntxt = cntxt;} public void ejbCreate(CustomerLocal cust, String stockSymbol, int numberOfShares, double priceCap, Date expiration){ setNumberOfShares(numberOfShares); setStockSymbol(stockSymbol); setPriceCap(priceCap); setExpiration(expiration); } public void ejbPostCreate(CustomerLocal cust, String stockSymbol, int numberOfShares, double priceCap, Date expiration){ setCustomer(cust); TimerService timerService = ejbCntxt.getTimerService(); timerService.createTimer( expiration, null ); } public void ejbTimeout(Timer timer){ cancelOrder(); } public void cancelOrder(){ setCanceled(true); setDateCanceled( new Date()); cancelTimer(); } public void executeOrder(){ setExecuted(true); setDateExecuted( new Date()); cancelTimer(); } private void cancelTimer(){ TimerService timerService = ejbCntxt.getTimerService(); Iterator timers = timerService.getTimers().iterator(); if(timers.hasNext() ){ Timer timer = (Timer) timers.next(); timer.cancel(); } } // EJB callback methods, persistent fields, and relationships fields not shown }
Cuando el contenedor llama al método ejbTimeout(), este simplemente llama al método cancelOrder() del propio bean, que a su vez configura de los campos persistentes para indicar que el pedido se ha cancelado y luego llama a un método privado cancelTimer(). Este método obtiene una referencia al temporizador real del bean y lo cancela. Se llama a este método cuando se ejecuta la orden de compra o cuando es cancelada por el temporizador, o cuando el cliente llama directamente al método cancelOrder().
Temporizadores de Beans de Entidad
Los beans de entidad configuran temporizadores sobre un tipo específico de bean de entidad (por ejemplo, Envío, Cliente, Reserva, etc.) con una clave primaria específica. Cuando el temporizador se acaba, lo primero que hace el contador es usar la clase primaria asociada con el temporizador para cargar el bean de entidad con los datos apropiados. Una vez que el bean de entidad está el estado ready -- sus datos se han cargado y está listo para servir peticiones --se llama al método ejbTimeout(). El contenedor asocia implícitamente la clave primaria con el temporizador.
Utilizar temporizadores con beans de entidad puede ser muy útil, porque permite a los beans de entidad manejar sus propios eventos programados. Esto tiene sentido, especialmente cuando los eventos temporizados son críticos para la definición de la entidad. Por ejemplo, el reclamo de pagos es intrínseco a la definición de una reclamación. Esto también es cierto para el tiempo límite de una compra-al-limite, una alerta de pago pasado de fecha en las hipotecas, etc.
Temporizadores en Beans de Sesión sin Estado
Los temporizadores de los beans de sesión sin estado se pueden utilizar para auditorias y procesamiento por lotes. Como un agente de auditoria, un temporizador de sesión sin estado puede monitorizar el estado del sistema para asegurarse de que se están completando las tareas y que los datos son consistentes. Este tipo de trabajo de auditoria se puede expandir a varias entidades y posiblemente a varias fuentes de datos. Como los EJB's también pueden realizar trabajos de procesamiento por lotes como la limpieza de una base de datos, la transferencia de registros, etc. los temporizadores de beans de sesión sin estado también se pueden desplegar como agentes que realizan algún tipo de trabajo inteligente para la organización a la que sirve. Un agente podría, por ejemplo, auditar el sistema que monitoriza pero también corregir los problemas de forma automática.
Mientras los temporizadores de entidad están asociados con un tipo específico de beans de entidad y con una clave primaria, los temporizadores de los beans de sesión sin estado sólo están asociados con un tipo específico de bean de sesión. Cuando se acaba un temporizador de un bean de sesión, el contenedor automáticamente selecciona un ejemplar arbitrario del bean sin estado desde el almacén de ejemplares y llama a su método ejbTimeout().
Temporizadores de Beans Dirigidos a Mensaje
Los temporizadores de beans dirigidos a mensaje son similares a los temporizadores de los beans de sesión en varias cosas: Los temporizadores están asociado sólo con un tipo de bean. Cuando un temporizador expira, se selecciona un bean dirigido a mensaje del almacén y se ejecuta su método ejbTimeout(). Además se puede utilizar el bean dirigido a mensaje para realizar auditorios y otros tipos de trabajos por lotes.
La principal diferencia entre estos temporizadores que la forma en que se inician: los temporizadores se crean en respuesta a un mensaje entrante o, si el contenedor lo soporta, desde un fichero de configuración.
Problemas con el Servicio Timer
El servicio Timer es una adición excelente a la plataforma EJB, pero está demasiado limitado. Se puede aprender mucho de cron , la utilidad de temporización de UNIX que lleva funcionando años.
Un poco sobre cron
Cron es un programa UNIX que nos permite temporizar scripts (similares a los ficheros batch del DOS), comandos y otros programas para se ejecuten en fechas y horas específicas. Al contrario que el Servicio Timer de EJB, cron permite una temporización muy flexible basada en calendario. Los trabajos cron (cualquier cosa que cron ejecute se llama un trabajo) se pueden temporizar para ejecutarse a intervalos de un minuto específico de cada hora, hora del día, día de la semana, día del mes, y mes del año.
Por ejemplo, podemos temporizar un trabajo cron para que se ejecute cada viernes a las 12:15 p.m., o cada hora, el primer día de cada mes. Aunque este nivel de refinamiento podría sonar complicado, realmente es muy sencillo de especificar. cron utiliza un sencillo formato de texto de cinco campos de valores enteros, separado por espacios o tabuladores, para describir los intervalos en los que se debería ejecutar el script. La siguiente figura muestra las posiciones de los campos y sus significados:

El orden de los campos es importante, ya que cada uno especifica un designador en el calendario: minuto, hora, día, mes y día de la semana.
Los siguientes ejemplos muestra cómo temporizar trabajos cron:
20 * * * * ---> 20 después de cada hora . (00:20, 01:20, etc.) 5 22 * * * ---> cada día a las 10:05 p.m. 0 8 1 * * ---> Primer día de cada mes a a las 8:00 a.m. 0 8 4 7 * ---> El cuatro de Julio a las 8:00 a.m. 15 12 * * 5 ---> Cada Viernes a las 12:15 p.m.
Un asterisco indica que todos los valores son válido. Por ejemplo, si usamos un asterisco en el campo minuto, estamos especificando que se ejecute el trabaja cada minuto d ela hora. Los siguientes ejemplos ilustran el impacto del asterisco en las temporizaciones:
* 10 * * * ---> Cada día a las 10:00, 10:01, ...10:59 0 10 * 7 * ---> Todos los días del mes de Julio a las 10:00 a.m. * * * * * ---> A cada minuto que la máquina esté conectada.
Podemos definir intervalos más complejos especificando varios valores, separados por comas, para un sólo ampo. Además podemos especificar rangos usando el guión:
0 8 * * 1,3,5 ---> Cada Lunes, Miércoles y Viernes a las 8:00 a.m. 0 8 1,15 * * ---> Los días 1 y 15 de cada mes a las 8:00 a.m. 0 8-17 * * 1-5 ---> A cada hora entre las 8 8 a.m. y las 5 p.m., de Lunes a Viernes
Los trabajos cron se temporizan usando fichero crontab, que son simples ficheros de texto en los que configuramos los campos de fecha/hora y un comando, normalmente un comando para ejecutar un script.
Mejorar el Servicio Timer
El formato fecha/hora de cron ofrece muchas más flexibilidad que la ofrecida por el Servicio Timer de EJB. Este servicio requiere que designemos los intervalos en milisegundos exactos, que se un poco trabajoso (tenemos que convertir días, horas y minutos a milisegundos), pero más importante es que no es lo suficientemente flexible para las necesidades de temporización del mundo real. Por ejemplo, no hay forma para hacer que algo se ejecuta los días 1 y 15 de cada más, o cada hora entre las 8:00 y las 17:00, de lunes a viernes. Podemos derivar algunos de los intervalos más complejos, pero sólo con el coste de añadir lógica al código de nuestro bean para calcularlos, y en escenarios más complicados necesitaremos varios temporizadores para la misma tarea.
Cron tampoco es perfecto. Temporizar trabajos es como seleccionar un temporizador en un aparato de Vídeo: todo se temporiza de acuerdo al reloj y al calendario. Podemos especificar que cron ejecute un trabajo en momentos específicos del día en días específicos del año, pero no podemos hacer que ejecute un trabajo a intervalos relativos desde un punto de inicio arbitrario. Por ejemplo, el formato de fecha/hora de cron no nos permite programar un trabajo cada 10 minutos empezando desde ahora mismo. Tenemos que temporizar el trabajo en minutos específicos de la hora (por ejemplo: 0,10,20,30,40,50).
Cron está limitando para temporizar trabajos recurrentes, no podemos configurar un temporizador con una sola acción, y tampoco podemos configurar una fecha de inicio. Un problema tanto con cron como con el Servicio Timer de EJB es que no podemos programar una fecha de parada -- una fecha en el temporizador se cancelara a sí mismo.
También podrías haber observado que la resolución de cron es al minuto y no al milisegundo. A primera vista parece una debilidad, pero en la práctica es perfectamente aplicable. Para temporizador basada en calendario, tener más precisión simplemente no es útil.
Una solución es cambiar el interface del Servicio Timer para que pueda manejar un formato de fecha/hora parecido al de cron, con una fecha de inicio y una fecha de fin. En vez de descartar las llamadas actuales a createTimer() (que son útiles, especialmente para temporizaciones de una sola acción y para intervalos de milisegundos arbitrarios), sería preferible simplemente añadir un nuevo método con la semántica deseada al estilo cron. Además, en lugar de utilizar 0-6 para designar el día de la semana, sería mejor seguir el conjunto de reglas de la versión Linux de cron, que usa los valores Sun, Mon, Tue, Wed, Thu, Fri, y Sat. Por ejemplo el código para programar un temporizador que se ejecutara cada día de la semana a las 11:00 p.m. empezando el 1 de Octubre de 2003, y terminando el 31 de Mayo de 2004, se parecería a esto:
TimerService timerService = ejbContext.getTimerService(); Calendar start = Calendar.getInstance().set(2003, Calendar.OCTOBER, 1); Calendar end = Calendar.getInstance().set(2004, Calendar.MAY, 31); String dateTimeString = "23 * * * Mon-Fri"; timerService.createTimer(dateTimeString, start, end, null);
Este cambio propuesto para el Servicio Timer retiene explícitamente los otros métodos createTimer() basados en milisegundos, porque son muy útiles.
Debería observarse, sin embargo, que la verdadera precisión al milisegundo es muy difícil porque (a) el procesamiento normal y la contención de threads tiende a retrasar el tiempo de respuesta, y (b) el reloj del servidor debe estar sincronizado apropiadamente con la hora real (es decir, UTC) con los milisegundos, y la mayoría no lo están. |
Debemos tener en cuenta que esto es sólo una proposición que debe valorar el grupo de expertos que trabaja con EJB.
Temporizadores de Beans Dirigidos a Mensaje: Propiedades de Configuración Estándar
Hay un enorme potencial en el uso de beans dirigidos a mensaje como trabajo al estilo cron que son configurados en el momento del despliegue para ejecutarse automáticamente. Desafortunadamente, no hay una forma estándar para configurar un temporizador de bean dirigido a mensaje durante el despliegue. Algunos vendedores podría soportar esto y otros no. Los temporizadores de bean dirigido a mensaje pre-configurados van a incrementar su demanda por parte de los desarrolladores que quieren hacer que los beans dirigidos a mensaje realicen un trabajo en fechas y horas específicas. Sin soporte para la configuración durante el despliegue, la una forma fiable de programar un temporizador de bean enterprise es hacer que un cliente llame a método o envíe un mensaje JMS. Este no es aceptable. Los desarrolladores necesitan configuración en tiempo de despliegue y se debería añadir en la próxima versión de la especificación.
Construido sobre la semántica al estilo cron propuesta en la sección anterior, sería fácil crear unas propiedades de configuración estándar para configurar los temporizadores de beans dirigidos a mensaje en tiempo de despliegue. Por ejemplo, abajo podemos ver como configurar un bean dirigido a mensaje, el EJB Audit, para ejecutarse a las 11:00 de lunes a viernes, empezando el 1 de Octubre de 2003 y terminando el 31 de Mayo de 2004 ( las fechas de inicio y final no son obligatorias):
<activation-config> <description>Run Monday through Friday at 11:00 p.m. Starting on Oct 1st,2003 until May 31st, 2004</description> <activation-config-property> <activation-config-property-name>dateTimeFields</activation-config-property-name> <activation-config-property-value> 23 * * * Mon-Fri</activation-config-property-value> </activation-config-property> <activation-config-property> <activation-config-property-name>startDate</activation-config-property-name> <activation-config-property-value>October 1, 2003</activation-config-property-value> </activation-config-property> <activation-config-property> <activation-config-property-name>endDate</activation-config-property-name> <activation-config-property-value>May 31, 2004</activation-config-property-value> </activation-config-property> </activation-config>
Esta configuración sería bastante fácil de implementar por los proveedores si soportaran la semántica mejora al estilo cron. Además podríamos configurar los beans dirigidos a mensaje para usar los temporizadores basados en milisegundos que ya soporta EJB 2.1.
Otros problemas con el API Timer
La semántica del objeto Timer contiene muy poca información sobre el propio objeto timer. No hay forma de determinar si un temporizador es de una sola acción o un temporizador de intervalos. Si es un temporizador de intervalos, no hay forma de determinar el intervalo configurado, o si el temporizador a ejecutado su primera expiración o no. Para solucionar estos problemas, se deberían añadir métodos adicionales al interface Timer que proporcionen esta información. Sería una buena idea que esta información se situara en el objeto info, para que se pueda acceder a ella desde cualquier aplicación que lo necesite.