package org.ovirt.engine.core.utils.timer;
import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobKey.jobKey;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.TriggerKey.triggerKey;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class SchedulerUtilBaseImpl implements SchedulerUtil {
public static final String RUNNABLE_INSTANCE = "runnable.instance";
public static final String RUN_METHOD_NAME = "method.name";
public static final String RUN_METHOD_PARAM_TYPE = "method.paramType";
public static final String RUN_METHOD_PARAM = "method.param";
public static final String FIXED_DELAY_VALUE = "fixedDelayValue";
public static final String FIXED_DELAY_TIME_UNIT = "fixedDelayTimeUnit";
public static final String CONFIGURABLE_DELAY_KEY_NAME = "configDelayKeyName";
private static final String TRIGGER_PREFIX = "trigger";
protected final Logger log = LoggerFactory.getLogger(getClass());
protected Scheduler sched;
private final AtomicLong sequenceNumber = new AtomicLong(Long.MIN_VALUE);
public static Date getFutureDate(long delay, TimeUnit timeUnit) {
return new Date(System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(delay, timeUnit));
}
/**
* schedules a fixed delay job.
*
* @param instance
* - the instance to activate the method on timeout
* @param methodName
* - the name of the method to activate on the instance
* @param inputTypes
* - the method input types
* @param inputParams
* - the method input parameters
* @param initialDelay
* - the initial delay before the first activation
* @param taskDelay
* - the delay between jobs
* @param timeUnit
* - the unit of time used for initialDelay and taskDelay.
* @return the scheduled job id
*/
@Override
public String scheduleAFixedDelayJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
long initialDelay,
long taskDelay,
TimeUnit timeUnit) {
JobDetail job = createJobForDelayJob(instance, methodName, inputTypes, inputParams, taskDelay, timeUnit);
scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
return job.getKey().getName();
}
private void scheduleJobWithTrigger(long initialDelay, TimeUnit timeUnit, Object instance, JobDetail job) {
Trigger trigger = createSimpleTrigger(initialDelay, timeUnit, instance);
try {
sched.scheduleJob(job, trigger);
} catch (SchedulerException se) {
log.error("failed to schedule job: {}", se.getMessage());
log.debug("Exception", se);
}
}
private JobDetail createJobForDelayJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
long taskDelay,
TimeUnit timeUnit) {
JobDetail job = createJobWithBasicMapValues(instance, methodName, inputTypes, inputParams);
JobDataMap data = job.getJobDataMap();
setupDataMapForDelayJob(data, taskDelay, timeUnit);
return job;
}
/**
* schedules a job with a configurable delay.
*
* @param instance
* - the instance to activate the method on timeout
* @param methodName
* - the name of the method to activate on the instance
* @param inputTypes
* - the method input types
* @param inputParams
* - the method input parameters
* @param initialDelay
* - the initial delay before the first activation
* @param taskDelay
* - the name of the config value that sets the delay between jobs
* @param timeUnit
* - the unit of time used for initialDelay and taskDelay.
* @return the scheduled job id
*/
@Override
public String scheduleAConfigurableDelayJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
long initialDelay,
String configurableDelayKeyName,
TimeUnit timeUnit) {
long configurableDelay = getConfigurableDelay(configurableDelayKeyName);
JobDetail job =
createJobForDelayJob(instance, methodName, inputTypes, inputParams, configurableDelay, timeUnit);
JobDataMap data = job.getJobDataMap();
data.put(CONFIGURABLE_DELAY_KEY_NAME, configurableDelayKeyName);
scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
return job.getKey().getName();
}
/**
* get the configurable delay value from the DB according to given key
*/
private long getConfigurableDelay(String configurableDelayKeyName) {
ConfigValues configDelay = ConfigValues.valueOf(configurableDelayKeyName);
return Config.<Integer> getValue(configDelay).longValue();
}
/**
* setup the values in the data map that are relevant for jobs with delay
*/
private void setupDataMapForDelayJob(JobDataMap data, long taskDelay, TimeUnit timeUnit) {
data.put(FIXED_DELAY_TIME_UNIT, timeUnit);
data.put(FIXED_DELAY_VALUE, taskDelay);
}
protected abstract JobDetail createJobWithBasicMapValues(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams);
private Trigger createSimpleTrigger(long initialDelay, TimeUnit timeUnit, Object instance) {
Date runTime = getFutureDate(initialDelay, timeUnit);
String triggerName = generateUniqueNameForInstance(instance, TRIGGER_PREFIX);
Trigger trigger = newTrigger()
.withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
.startAt(runTime)
.build();
return trigger;
}
/**
* schedules a one time job.
*
* @param instance
* - the instance to activate the method on timeout
* @param methodName
* - the name of the method to activate on the instance
* @param inputTypes
* - the method input types
* @param inputParams
* - the method input parameters
* @param initialDelay
* - the initial delay before the job activation
* @param timeUnit
* - the unit of time used for initialDelay and taskDelay.
* @return the scheduled job id
*/
@Override
public String scheduleAOneTimeJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
long initialDelay,
TimeUnit timeUnit) {
JobDetail job = createJobWithBasicMapValues(instance, methodName, inputTypes, inputParams);
scheduleJobWithTrigger(initialDelay, timeUnit, instance, job);
return job.getKey().getName();
}
/**
* schedules a cron job.
*
* @param instance
* - the instance to activate the method on timeout
* @param methodName
* - the name of the method to activate on the instance
* @param inputTypes
* - the method input types
* @param inputParams
* - the method input parameters
* @param cronExpression
* - cron expression to run this job
* @return the scheduled job id
*/
@Override
public String scheduleACronJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
String cronExpression) {
JobDetail job = createJobWithBasicMapValues(instance, methodName, inputTypes, inputParams);
try {
String triggerName = generateUniqueNameForInstance(instance, TRIGGER_PREFIX);
Trigger trigger = newTrigger()
.withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
.withSchedule(cronSchedule(cronExpression))
.build();
sched.scheduleJob(job, trigger);
} catch (Exception se) {
log.error("failed to schedule job: {}", se.getMessage());
log.debug("Exception", se);
return null;
}
return job.getKey().getName();
}
/**
* Schedules a cron job with specific delay and end by value
*
* @param instance
* - the instance to activate the method on timeout
* @param methodName
* - the name of the method to activate on the instance
* @param inputTypes
* - the method input types
* @param inputParams
* - the method input parameters
* @param cronExpression
* - cron expression to run this job
* @param startAt
* - when to start the task
* @param endBy
* - when to end the task
* @return the scheduled job id
*/
public String scheduleACronJob(Object instance,
String methodName,
Class<?>[] inputTypes,
Object[] inputParams,
String cronExpression,
Date startAt,
Date endBy) {
JobDetail job = createJobWithBasicMapValues(instance, methodName, inputTypes, inputParams);
try {
String triggerName = generateUniqueNameForInstance(instance, TRIGGER_PREFIX);
Trigger trigger = newTrigger()
.withIdentity(triggerName, Scheduler.DEFAULT_GROUP)
.withSchedule(cronSchedule(cronExpression))
.startAt(startAt)
.endAt(endBy)
.build();
sched.scheduleJob(job, trigger);
} catch (Exception se) {
log.error("failed to schedule job: {}", se.getMessage());
log.debug("Exception", se);
throw new RuntimeException(se);
}
return job.getKey().getName();
}
/**
* reschedule the job associated with the given old trigger with the new trigger.
*
* @param oldTriggerName
* - the name of the trigger to remove.
* @param oldTriggerGroup
* - the group of the trigger to remove.
* @param newTrigger
* - the new Trigger to associate the job with
*/
@Override
public void rescheduleAJob(String oldTriggerName, String oldTriggerGroup, Trigger newTrigger) {
try {
if (!sched.isShutdown()) {
sched.rescheduleJob(triggerKey(oldTriggerName, oldTriggerGroup), newTrigger);
}
} catch (SchedulerException se) {
log.error("failed to reschedule the job: {}", se.getMessage());
log.debug("Exception", se);
}
}
/**
* pauses a job with the given jobId assuming the job is in the default quartz group
*/
@Override
public void pauseJob(String jobId) {
try {
sched.pauseJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
} catch (SchedulerException se) {
log.error("failed to pause a job with id={}: {}", jobId, se.getMessage());
log.debug("Exception", se);
}
}
/**
* Delete the identified Job from the Scheduler
*
* @param jobId
* - the id of the job to delete
*/
@Override
public void deleteJob(String jobId) {
try {
sched.deleteJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
} catch (SchedulerException se) {
log.error("failed to delete a job with id={}: {}", jobId, se.getMessage());
log.debug("Exception", se);
}
}
/**
* resume a job with the given jobId assuming the job is in the default quartz group
*/
@Override
public void resumeJob(String jobId) {
try {
sched.resumeJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
} catch (SchedulerException se) {
log.error("failed to pause a job with id={}: {}", jobId, se.getMessage());
log.debug("Exception", se);
}
}
@Override
public void triggerJob(String jobId) {
try {
List<? extends Trigger> existingTriggers = sched.getTriggersOfJob(jobKey(jobId, Scheduler.DEFAULT_GROUP));
if (!existingTriggers.isEmpty()) {
// Note: we assume that every job has exactly one trigger
Trigger oldTrigger = existingTriggers.get(0);
TriggerKey oldTriggerKey = oldTrigger.getKey();
Trigger newTrigger = newTrigger()
.withIdentity(oldTriggerKey)
.startAt(getFutureDate(0, TimeUnit.MILLISECONDS))
.build();
rescheduleAJob(oldTriggerKey.getName(), oldTriggerKey.getGroup(), newTrigger);
} else {
log.error("failed to trigger a job with id={}, job has no trigger", jobId);
}
} catch (SchedulerException se) {
log.error("failed to trigger a job with id={}: {}", jobId, se.getMessage());
log.debug("Exception", se);
}
}
/**
* Halts the Scheduler, and cleans up all resources associated with the Scheduler. The scheduler cannot be
* re-started.
*
* @see org.quartz.Scheduler#shutdown(boolean waitForJobsToComplete)
*/
@Override
public void shutDown() {
try {
if (sched != null) {
sched.shutdown(false);
}
} catch (SchedulerException se) {
log.error("failed to shut down the scheduler: {}", se.getMessage());
log.debug("Exception", se);
}
}
/**
* @return the quartz scheduler wrapped by this SchedulerUtil
*/
@Override
public Scheduler getRawScheduler() {
return sched;
}
protected String generateUniqueNameForInstance(Object instance, String nestedName) {
String name = instance.getClass().getName() + "." + nestedName + "#" + sequenceNumber.incrementAndGet();
return name;
}
}