Planificación de tareas en Java mediante Quartz

Planificación de tareas en Java mediante Quartz

Introducción

Normalmente los negocios requieren que en determinados momentos se ejecuten tareas de forma automática, por ejemplo: "todos los viernes a las 15:00 enviar informes a clientes".
Crear un sistema robusto y completo que de soporte a la ejecución de esas tareas, además de no ser una tarea fácil, sería como reinventar la rueda pues ya disponemos de frameworks maduros de libre uso como el que vamos a presentar en este tutorial.
Quartz es un framework open source, con licencia Apache 2.0 para la planificación y gestión de tareas.
Es usado activamente en conocidos proyectos y organizaciones como JBoss, Cocoon, Apache Jakarta,... ver más

Características principales

  1. Válido para aplicaciones tanto J2EE como J2SE.
  2. Planificación flexible de tareas, por ejemplo: el primer lunes de Enero de cada año a las 17:45.
  3. Mantenimiento del estado de las tareas incluso en caso de fallos y reinicios de máquinas.
  4. Posibilidad de participar en transacciones JTA.
  5. Posibilidad de trabajar en modo Clúster.
  6. Proporciona un completo API, con muchas clases de utilidad y muchos tipos Listener (JobListener, TriggerListener y SchedulerListener).

Principales clases e interfaces

A continuación describimos los componentes que lo compomen: Job, JobDetail, Trigger, JobStore, Scheduler.

org.quartz.Job

Definir una tarea es tan sencillo como implementar la interface org.quartz.Job cuya definición se muestra a continuación:
  1. package org.quartz;  
  2.   
  3. public interface Job {  
  4.   public void execute(JobExecutionContext context) throws JobExecuteException;  
  5. }  
Como podéis observar, simplemente debemos implementar un método y lanzar una excepción en caso de error para que Quarz reintente su ejecución o no, en función de la configuración que especifiquemos.

org.quartz.JobDetail

Es una clase que almacena propiedades de una determinada tarea.
Las tareas se clasifican en grupos de tareas y cada tarea tiene un nombre único dentro del grupo. JobDetail define estás y otras propiedades.
Gracias a esta clasificación podemos pausar, iniciar, detener, etc. tareas o grupos de tareas de manera independiente al resto.

org.quartz.Trigger

Es una clase abstracta que define los instantes en que la tarea debe ser ejecutada, por ejemplo todos los lunes a las 16:00 de la tarde.
Existen varias implementaciones de esta clase pero las más usadas son:
org.quartz.SimpleTrigger Permite especificar ejecuciones de tareas teniendo en cuenta los siguientes parámetros: fecha, hora, nº de repeticiones e intervalo entre repeticiones.
Por ejemplo, ejecutar la tarea X 3 veces el día 13/12/1976 a las 13:30 con 40 minutos entre cada ejecución.

Para que se haga una idea, la firma de uno de sus constructores es:
  1. public SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval)  
org.quartz.CronTrigger Es el más utilizado, pues permite especificar mediante expresiones más complejas los instantes en los que deben ejecutarse las tareas.
Por ejemplo, todos los días 14 que caigan en jueves cada 5 minutos desde las 14:00 hasta las 18:00.
Aunque existe mucha funcionalidad de utilidad que permiten hacernos la vida más fácil a la hora de crear el CronTrigger, yo suelo usar un constructor que acepta una cadena de texto representando una expresión de tipo Cron de Unix: segundos, minutos, horas, días del mes, meses, días de la semana, [año] (observe que todos son obligatorios a excepción de el año).
"0 0 23 * * ?" La tarea será ejecutada todos los días a las 23:00
"0 20 15 * * 1 2007" La tarea será ejecutada todos los domingos del año 2007 a las 15:20
Para más información acerca de CronTrigger haga clic aquí.

org.quartz.JobStore

Interface para manejar el almacenamiento y recuperación de la información de planificación (tareas, triggers, etc).
Implementaciones de org.quartz.JobStore:
org.quartz.simpl.RAMJobStore Es la implementación de por defecto, almacena la información en memoria RAM por lo que cuando la aplicación finaliza no se guarda el estado.
org.quartz.impl.jdbcjobstore.JobStoreTX Implementación que almacena la información en una dase de datos a través de JDBC. Diseñado para entornos no transaccionales.
org.quartz.impl.jdbcjobstore.JobStoreCMP Implementación que almacena la información en una dase de datos a través de JDBC. Diseñado para entornos transaccionales.
La elección del JobStore se define a través de la propiedad org.quartz.jobStore.class de el archivo de propiedades de Quartz quartz.properties, por ejemplo.
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

org.quartz.Scheduler

Su funcionalidad es almacenar y planificar las tareas (Job) en base a los Triggers, recuperar tareas fallidas, realizar reintentos y gestionar el estado del sistema de planificación.

Ejemplo. Planificación de tareas en modo programático.

A continuación y a modo de ejemplo vamos a crear una aplicación que lance una tarea cada 5 segundos.
Aunque la tarea podría realizar cualquier operación compleja, en este caso simplemente mostrará un mensaje por pantalla.

com.autentia.tutoriales.quartz.StatusMonitorJob.java

Implementación de la tarea:
  1. package com.autentia.tutoriales.quartz;  
  2.   
  3. /** 
  4.  * Tarea de ejemplo. 
  5.  * Guarda en un archivo la memoria total y libre de la máquina virtual 
  6.  * @author Carlos García. Autentia Real Business Solutions 
  7.  * @see http://www.mobiletest.es 
  8.  */  
  9. public class StatusMonitorJob implements org.quartz.Job {  
  10.     public static final String TARGET_FILE_PROP = "outputFile";  
  11.       
  12.     /*  
  13.      * La tarea es ejecutada 
  14.      * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) 
  15.      */  
  16.     public void execute(org.quartz.JobExecutionContext context) throws org.quartz.JobExecutionException {  
  17.         org.apache.log4j.Logger logger  = org.apache.log4j.Logger.getLogger(StatusMonitorJob.class);  
  18.           
  19.         if (logger.isDebugEnabled()){  
  20.             logger.debug("Executing");  
  21.         }  
  22.           
  23.         StringBuffer            statusLine  = new StringBuffer(128);  
  24.         java.io.FileWriter      writer      = null;  
  25.           
  26.         // Obtenemos la propiedad que especifica el archivo donde se desea guardar el estado de la VM, proporcionando un   
  27.         // valor por defecto en caso de que no se haya especificado  
  28.         org.quartz.JobDataMap   properties  = context.getJobDetail().getJobDataMap();  
  29.         String                  outputFile  = properties.getString(StatusMonitorJob.TARGET_FILE_PROP);  
  30.         if ((outputFile == null) || ("".equals(outputFile.trim()))){  
  31.             outputFile = "status.txt";  
  32.         }  
  33.           
  34.         try {  
  35.             // Escribimos en el archivo una linea de estado con formato: INSTANTE TOTAL_MEMORY FREE_MEMORY            
  36.             writer = new java.io.FileWriter(outputFile, true);   
  37.    
  38.             statusLine.append(System.currentTimeMillis());  
  39.             statusLine.append('\t');  
  40.             statusLine.append(Runtime.getRuntime().totalMemory());  
  41.             statusLine.append('\t');  
  42.             statusLine.append(Runtime.getRuntime().freeMemory());  
  43.             statusLine.append(System.getProperty("line.separator"));  
  44.               
  45.             if (logger.isDebugEnabled()){  
  46.                 logger.debug("Storing VM state to file: " + outputFile);  
  47.             }  
  48.               
  49.             writer.write(statusLine.toString());  
  50.         } catch (java.io.IOException e) {  
  51.             logger.error("Error ", e);  
  52.         } finally {  
  53.             try {  
  54.                 writer.close();  
  55.             } catch (Exception ex){}  
  56.         }  
  57.     }  
  58.       
  59. }     
  60.       

com.autentia.tutoriales.quartz.StatusMonitor.java

Aplicación de escritorio que realiza la planificación de la tarea:
  1. package com.autentia.tutoriales.quartz;  
  2.   
  3. /** 
  4.  * Planificación de tareas en Java mediante Quartz 
  5.  * @author Carlos García. Autentia Real Business Solutions 
  6.  * @see http://www.mobiletest.es  
  7.  */  
  8. public class StatusMonitor  {  
  9.     private org.quartz.Scheduler scheduler;  
  10.       
  11.     /** 
  12.      * Instancia, configura e inicia la tarea para ser ejecutada cada 5 segundos. 
  13.      * @throws org.quartz.SchedulerException 
  14.      */  
  15.     public void start() throws org.quartz.SchedulerException {  
  16.         try {  
  17.             // Definimos la tarea (nombre de la tarea, nombre del grupo de tareas, Clase que implementa la tarea)  
  18.             org.quartz.JobDetail    jobDetail = new org.quartz.JobDetail("StatusJob", org.quartz.Scheduler.DEFAULT_GROUP, StatusMonitorJob.class);  
  19.   
  20.             // Configuramos los parametros de la tarea, en esta caso le decimos en que archivo debe guardar el estado de la VM.  
  21.             jobDetail.getJobDataMap().put(StatusMonitorJob.TARGET_FILE_PROP, "vm_status.txt");  
  22.               
  23.             // Configuramos el Trigger que avisará al planificador de cuando debe ejecutar la tarea, en este caso cada 5 segundos.  
  24.             org.quartz.CronTrigger trigger   = new org.quartz.CronTrigger("StatusTrigger", org.quartz.Scheduler.DEFAULT_GROUP, "0/5 0 0 * * * ?");  
  25.               
  26.             // Obtenemos el planificador  
  27.             scheduler = org.quartz.impl.StdSchedulerFactory.getDefaultScheduler();  
  28.               
  29.             // La tarea definida en JobDetail será ejecutada en los instantes especificados por el Trigger.  
  30.             scheduler.scheduleJob(jobDetail, trigger);  
  31.               
  32.             // Iniciamos las tareas planificadas en el Sheduler  
  33.             scheduler.start();  
  34.         } catch (java.text.ParseException e) {  
  35.             // No se dará  
  36.         }  
  37.     }  
  38.       
  39.     /** 
  40.      * Detiene el proceso de planificación 
  41.      */  
  42.     public void stop(){  
  43.         try {  
  44.             scheduler.shutdown();  
  45.         } catch (Exception ex) {  
  46.             // Nada  
  47.         }             
  48.     }  
  49.       
  50.     /** 
  51.      * Punto de entrada a la aplicación 
  52.      */  
  53.     public static void main(String[] args)  {  
  54.         StatusMonitor monitor = new StatusMonitor();  
  55.           
  56.         try {  
  57.             monitor.start();  
  58.           
  59.             System.out.println("Pausa... pulse una tecla para finalizar la aplicación");  
  60.             System.in.read();  
  61.               
  62.             monitor.stop();           
  63.         } catch (Exception ex) {  
  64.             System.err.println(ex);  
  65.         }  
  66.     }  
  67.       
  68. }     
  69.       

Ejemplo. Planificación de tareas en modo declarativo.

Por defecto, en la configuración de Quartz puede especificar tareas externamente a la aplicación en un archivo de nombre quartz_jobs.xml
Este método le ofrece una gran flexibilidad, ya que podrá añadir o eliminar tareas sin realizar la gestión de planificación dentro de la aplicación... simplemente deberá copiar las clases en el CLASSPATH de la máquina virtual Java.

quartz_jobs.xml

  1. xml version="1.0" encoding="utf-8"?>  
  2. <quartz>  
  3.     <job>  
  4.         <job-detail>  
  5.             <name>QuartzTarea1name>  
  6.             <group>DEFAULTgroup>  
  7.             <description>Esta es una tarea de ejemplo del uso de Quartzdescription>  
  8.             <job-class>com.autentia.tutoriales.quartz.MyTaskjob-class>  
  9.             <volatility>falsevolatility>  
  10.             <durability>falsedurability>  
  11.             <recover>falserecover>  
  12.             <job-data-map allows-transient-data="true">  
  13.                 <entry>  
  14.                     <key>propiedad1key>  
  15.                     <value>valor_propiedad1value>  
  16.                 entry>  
  17.             job-data-map>  
  18.         job-detail>  
  19.         <trigger>  
  20.             <simple>  
  21.                 <name>QuartzTrigger1name>  
  22.                 <group>DEFAULTgroup>  
  23.                 <job-name>QuartzTrigger1job-name>  
  24.                 <job-group>DEFAULTjob-group>  
  25.                 <start-time>2005-09-17 0:10:00 PMstart-time>  
  26.                 <repeat-count>-1repeat-count>  
  27.                 <repeat-interval>1000repeat-interval>  
  28.             simple>  
  29.         trigger>  
  30.     job>  
  31. quartz>      
  32.       

Aplicación Web para gestionar nuestras tareas (modo declarativo)

En el siguiente enlace http://prdownloads.sourceforge.net/quartz/quartz-web-app.zip puede obtener una útil aplicación Web para gestionar las tareas a través de una completa aplicación Web. Su uso queda fuera del alcance de este tutorial....
Los datos de acceso de por defecto son quartz tanto para el nombre de usuario como para la contraseña.

Direcciones de interés (en inglés)

Conclusiones

Quartz es un FrameWork maduro y muy extendido, que nos proporciona ampliar la funcionalidad de nuestros proyectos que basan en tareas de una forma fácil y flexible.
Sin lugar a dudas, Quartz es una opción a tener en cuenta en estos casos... Para más información diríjase a la documentación del proyecto.

Comentarios