En varias oportunidades he tenido que usar Quartz en mis aplicaciones, especialmente en los casos en que he necesitado que mis servicios web fueran asíncronos de forma tal que el cliente no se quedara esperando una respuesta.
Normalmente, Quartz viene con un servicio (un .sar) para JBoss. El servicio se integra bien y permite tener la configuración de Quartz aislada en un solo archivo dentro del sar.
Gracias a dicho servicio, dentro de nuestras aplicaciones podemos usar JBoss obteniendo el Scheduler guardado en JNDI.
[cc lang="java"]
Context context = new InitialContext();
sched = (Scheduler) context.lookup(”Quartz”);
jobDetail = new JobDetail(”checkRemoteIsWorking_” + remoteBrokerName, // job name
null, // job group (you can also specify ‘null’ to use the default group)
CheckRemoteIsWorking.class); // the java class to execute
jobDetail.getJobDataMap().put(”properties”, properties);
SimpleTrigger trigger = new SimpleTrigger(”trigger1″ + startTime, // name
“group1″, // group
new Date(startTime +
(Long.parseLong(properties.getProperty(”intervalo”)) * 1000L)), // startTime
null, // end Time
SimpleTrigger.REPEAT_INDEFINITELY, // repeat infinitely
(60L * 1000L) * Long.parseLong(properties.getProperty(
“intervalo”))); // repeat Interval
sched.scheduleJob(jobDetail, trigger);
sched.start();
[/cc]
Si bien esto es muy útil, suele haber problemas.
Dado que Quartz fue instanciado dentro del contexto del sar, si tengo una aplicación en otro contexto (por ej: una webapp) y la clase implementadora de Job que Quartz debe ejecutar esta dentro de la webapp, Quartz no podrá ver dicha clase y, al momento de intentar ejecutarla, lanzará una excepción de “Class Not Found”.
En algunos casos, la opción pasa por poner a la clase que implementa job dentro del mismo contexto que Quartz o en el directorio lib de la instancia de JBoss donde estemos trabajando. Sin embargo, esa opción no solo no es muy elegante sino que tampoco es útil si nuestra clase depende de otras (por ejemplo, si nuestra clase utiliza metodos de iBatis o Hibernate) ya que deberíamos copiar tambien otras clases y archivos de configuración al directorio lib del servidor.
La solución elegante y práctica que he encontrado pasa por hacer una nueva instancia de Quartz dentro de la webapp. De esa forma, nuestros jars estan aislados dentro de su propio contenedor y podemos configurar Quartz de acuerdo a las necesidades particulares de nuestra aplicación.
Como primera medida, debemos crear un archivo llamado quartz.properties en algun lugar dentro de WEB-INF, por ejemplo: WEB-INF/classes/quartz.properties.
[cc lang="bash"]
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
# org.quartz.scheduler.classLoadHelper.class =
org.quartz.scheduler.instanceName = AxisQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.xaTransacted = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 4
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = QUARTZ
org.quartz.jobStore.nonManagedTXDataSource = QUARTZ_NO_TX
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.dataSource.QUARTZ.jndiURL = java:/QuartzDS
org.quartz.dataSource.QUARTZ_NO_TX.jndiURL = java:/QuartzDS
[/cc]
En este caso, mi webapp es en realidad axis ya que mi aplicación es un web service dentro de axis. Como se puede ver, sigo utilizando el datasource de JBoss y defino mi instancia de Quartz con un nombre propio (AxisQuartzScheduler) lo cual me permitirá aislar e identificar problemas en las tareas lanzadas desde esta webapp.
Otra ventaja evidente es la de poder configurar el pool de threads para esta aplicación de forma indenpendiente y de acuerdo a necesidades particulares.
Ahora bien, nuestra webapp deberá instanciar Quartz ni bien arranque el servidor y para ello deberemos pasarle ciertos parámetros desde web.xml
[cc lang="xml"]
Una vez hecho esto, definiremos un listener para que instancie Quartz al iniciarse el servidor:
[cc lang="xml"]
[/cc]
Y dentro de nuestro listener haremos lo siguiente:
[cc lang="java"]
public class NotificationServiceManager implements ServletContextListener {
private static Logger logger = Logger.getLogger(NotificationServiceManager.class);
public static final String QUARTZ_FACTORY_KEY = “org.quartz.impl.StdSchedulerFactory.KEY”;
private boolean performShutdown = true;
private Scheduler scheduler = null;
public void contextInitialized(ServletContextEvent sce) {
if (logger.isInfoEnabled()) {
logger.info(”contextInitialized(ServletContextEvent) - start”); //$NON-NLS-1$
}
ServletContext servletContext = sce.getServletContext();
StdSchedulerFactory factory;
String prefixLogFile = sce.getServletContext().getRealPath(”/WEB-INF”);
try {
String configFile = prefixLogFile + servletContext.getInitParameter(”config-file”);
String shutdownPref = servletContext.getInitParameter(”shutdown-on-unload”);
logger.info(”config file: ” + configFile);
if (shutdownPref != null) {
performShutdown = Boolean.valueOf(shutdownPref).booleanValue();
}
// get Properties
if (configFile != null) {
logger.info(”NotificationServiceManager - getting ” + “quartz properties”);
factory = new StdSchedulerFactory(configFile);
} else {
logger.info(”NotificationServiceManager - No ” + “quartz properties”);
factory = new StdSchedulerFactory();
}
// Should the Scheduler being started now or later
String startOnLoad = servletContext.getInitParameter(”start-scheduler-on-load”);
scheduler = factory.getScheduler();
/**
* If the “start-scheduler-on-load” init-parameter is not specified,
* the scheduler will be started. This is to maintain backwards
* compatability.
*/
if (startOnLoad == null || (Boolean.getBoolean(startOnLoad))) {
// Start now
scheduler.start();
logger.info(”Scheduler has been started…”);
} else {
logger.info(”Scheduler has not been started. Use scheduler.start()”);
}
logger.info(”Storing the Quartz Scheduler Factory in the servlet context at key: ”
+ QUARTZ_FACTORY_KEY);
servletContext.setAttribute(QUARTZ_FACTORY_KEY, factory);
} catch (Exception e) {
logger.error(”Quartz Scheduler failed to initialize: ” + e.toString());
e.printStackTrace();
}
try {
AdminHelper.configure(sce.getServletContext().getRealPath(”/WEB-INF/”));
AdminHelper.setSf(scheduler);
} catch (Exception e) {
logger.error(”contextInitialized(ServletContextEvent)”, e); //$NON-NLS-1$
logger.error(”Unable to configure MOM elements: ” + e.getMessage());
e.printStackTrace();
}
if (logger.isInfoEnabled()) {
logger.info(”contextInitialized(ServletContextEvent) - end”); //$NON-NLS-1$
}
}
public void contextDestroyed(ServletContextEvent sce) {
if (!performShutdown)
return;
try {
if (scheduler != null)
scheduler.shutdown();
} catch (Exception e) {
logger.error(”Quartz Scheduler failed to shutdown cleanly: ” + e.toString());
e.printStackTrace();
}
logger.info(”NotificationServiceManager - Quartz Scheduler successful shutdown.”);
}
}
[/cc]
Normalmente, el scheduler se pasa al resto de la webapp via el contexto. En mi caso, dado que mi aplicación es un servicio web, he creado una instancia singleton de una clase AdminHelper a la que uso para almacenar todos los datos de configuración de mi aplicación. En este caso, aprovecho AdminHelper para pasarle la instancia de Quartz que he iniciado en el listener.
Luego, dentro de mi aplicación podré obtener el shceduler de la siguiente manera:
[cc lang="java"]
Scheduler sched = (Scheduler) AdminHelper.getSf();
[/cc]
De esta forma, Quartz verá sin problemas a todas mis clases “job” dentro de la webapp.

Esta obra está bajo una licencia de Creative Commons.
RSS feed for comments on this post · TrackBack URI
Leave a reply