net.joelinn.quartz.jobstore.RedisJobStore Maven / Gradle / Ivy
Show all versions of quartz-redis-jobstore Show documentation
package net.joelinn.quartz.jobstore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.joelinn.quartz.jobstore.jedis.JedisClusterCommandsWrapper;
import net.joelinn.quartz.jobstore.mixin.CronTriggerMixin;
import net.joelinn.quartz.jobstore.mixin.HolidayCalendarMixin;
import net.joelinn.quartz.jobstore.mixin.JobDetailMixin;
import net.joelinn.quartz.jobstore.mixin.TriggerMixin;
import org.quartz.Calendar;
import org.quartz.*;
import org.quartz.impl.calendar.HolidayCalendar;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.spi.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.util.Pool;
import java.util.*;
/**
* Joe Linn
* 7/12/2014
*/
@SuppressWarnings("unchecked")
public class RedisJobStore implements JobStore {
private static final Logger logger = LoggerFactory.getLogger(RedisJobStore.class);
private Pool jedisPool;
private JedisClusterCommandsWrapper jedisCluster;
/**
* Redis lock timeout in milliseconds
*/
protected int lockTimeout = 30_000;
/**
* Redis host
*/
protected String host;
/**
* Redis port
*/
protected int port = 6379;
/**
* Redis password
*/
protected String password;
/**
* Redis database
*/
protected short database = 0;
/**
* Redis sentinel master group name
*/
protected String masterGroupName;
/**
* Redis key prefix
*/
protected String keyPrefix = "";
/**
* Redis key delimiter
*/
protected String keyDelimiter = ":";
/**
* Set to true if a {@link JedisCluster} should be used. {@link #host} will be split on ',', and the resulting
* strings will be used as hostanmes for the cluster nodes.
*/
private boolean redisCluster;
/**
* Set to true if a {@link JedisSentinelPool} should be used. {@link #host} will be split on ',', and the
* resulting strings will be used as hostnames for the sentinel nodes. {@link #masterGroupName} will be
* used as the master group name.
*/
private boolean redisSentinel;
private int misfireThreshold = 60_000;
protected String instanceId;
protected AbstractRedisStorage storage;
/**
* socket connection timeout in ms
*/
protected int conTimeout = 3000;
/**
* connection retries counter
*/
protected int conRetries = 5;
/**
* socket timeout in ms
*/
protected int soTimeout = 3000;
/**
* ssl flag
*/
protected boolean ssl = false;
/**
* Time in MILLISECONDS after which an inactive clustered scheduler will be considered dead.
*/
protected long clusterCheckinInterval = 4 * 60 * 1000;
public RedisJobStore setJedisPool(Pool jedisPool) {
this.jedisPool = jedisPool;
return this;
}
public RedisJobStore setJedisCluster(JedisClusterCommandsWrapper jedisCluster) {
this.jedisCluster = jedisCluster;
return this;
}
public void setMisfireThreshold(int misfireThreshold) {
this.misfireThreshold = misfireThreshold;
}
public void setClusterCheckinInterval(long interval) {
this.clusterCheckinInterval = interval;
}
/**
* Called by the QuartzScheduler before the JobStore
is
* used, in order to give the it a chance to initialize.
*
* @param loadHelper class loader helper
* @param signaler schedule signaler object
*/
@Override
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
final RedisJobStoreSchema redisSchema = new RedisJobStoreSchema(keyPrefix, keyDelimiter);
ObjectMapper mapper = new ObjectMapper()
.addMixIn(CronTrigger.class, CronTriggerMixin.class)
.addMixIn(SimpleTrigger.class, TriggerMixin.class)
.addMixIn(JobDetail.class, JobDetailMixin.class)
.addMixIn(HolidayCalendar.class, HolidayCalendarMixin.class)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
if (loadHelper != null && loadHelper.getClassLoader() != null) {
mapper.setTypeFactory(mapper.getTypeFactory().withClassLoader(loadHelper.getClassLoader()));
}
if (redisCluster && jedisCluster == null) {
Set nodes = buildNodesSetFromHost();
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisCluster = new JedisClusterCommandsWrapper(new JedisCluster(nodes, this.conTimeout, this.soTimeout, this.conRetries, this.password,jedisPoolConfig));
storage = new RedisClusterStorage(redisSchema, mapper, signaler, instanceId, lockTimeout);
} else if (jedisPool == null) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setTestOnBorrow(true);
if (redisSentinel) {
Set nodes = buildNodesSetFromHost();
Set nodesAsStrings = new HashSet<>();
for (HostAndPort node : nodes) {
nodesAsStrings.add(node.toString());
}
if (logger.isDebugEnabled()) {
logger.debug("Instantiating JedisSentinelPool using master " + masterGroupName + " and hosts " + host);
}
jedisPool = new JedisSentinelPool(masterGroupName, nodesAsStrings, jedisPoolConfig, Protocol.DEFAULT_TIMEOUT, password, database);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Instantiating JedisPool using host " + host + " and port " + port);
}
jedisPool = new JedisPool(jedisPoolConfig, host, port, Protocol.DEFAULT_TIMEOUT, password, database, ssl);
}
storage = new RedisStorage(redisSchema, mapper, signaler, instanceId, lockTimeout);
}
storage.setMisfireThreshold(misfireThreshold)
.setClusterCheckInterval(clusterCheckinInterval);
}
/**
* Called by the QuartzScheduler to inform the JobStore
that
* the scheduler has started.
*/
@Override
public void schedulerStarted() throws SchedulerException {
}
/**
* Called by the QuartzScheduler to inform the JobStore
that
* the scheduler has been paused.
*/
@Override
public void schedulerPaused() {
// nothing to do
}
/**
* Called by the QuartzScheduler to inform the JobStore
that
* the scheduler has resumed after being paused.
*/
@Override
public void schedulerResumed() {
// nothing to do
}
/**
* Called by the QuartzScheduler to inform the JobStore
that
* it should free up all of it's resources because the scheduler is
* shutting down.
*/
@Override
public void shutdown() {
if(jedisPool != null){
jedisPool.destroy();
}
}
@Override
public boolean supportsPersistence() {
return true;
}
/**
* How long (in milliseconds) the JobStore
implementation
* estimates that it will take to release a trigger and acquire a new one.
*/
@Override
public long getEstimatedTimeToReleaseAndAcquireTrigger() {
return 100;
}
/**
* Whether or not the JobStore
implementation is clustered.
*/
@Override
public boolean isClustered() {
return true;
}
/**
* Store the given {@link org.quartz.JobDetail}
and {@link org.quartz.Trigger}
.
*
* @param newJob The JobDetail
to be stored.
* @param newTrigger The Trigger
to be stored.
* @throws org.quartz.ObjectAlreadyExistsException if a Job
with the same name/group already
* exists.
*/
@Override
public void storeJobAndTrigger(final JobDetail newJob, final OperableTrigger newTrigger) throws ObjectAlreadyExistsException, JobPersistenceException {
try {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.storeJob(newJob, false, jedis);
storage.storeTrigger(newTrigger, false, jedis);
return null;
}
});
} catch (ObjectAlreadyExistsException e) {
logger.info("Job and / or trigger already exist in storage.", e);
throw e;
} catch (Exception e) {
logger.error("Could not store job.", e);
throw new JobPersistenceException(e.getMessage(), e);
}
}
/**
* Store the given {@link org.quartz.JobDetail}
.
*
* @param newJob The JobDetail
to be stored.
* @param replaceExisting If true
, any Job
existing in the
* JobStore
with the same name & group should be
* over-written.
* @throws org.quartz.ObjectAlreadyExistsException if a Job
with the same name/group already
* exists, and replaceExisting is set to false.
*/
@Override
public void storeJob(final JobDetail newJob, final boolean replaceExisting) throws ObjectAlreadyExistsException, JobPersistenceException {
try {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.storeJob(newJob, replaceExisting, jedis);
return null;
}
});
} catch (ObjectAlreadyExistsException e) {
logger.info("Job hash already exists");
throw e;
} catch (Exception e) {
logger.error("Could not store job.", e);
throw new JobPersistenceException(e.getMessage(), e);
}
}
@Override
public void storeJobsAndTriggers(final Map> triggersAndJobs, final boolean replace) throws ObjectAlreadyExistsException, JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
for (Map.Entry> entry : triggersAndJobs.entrySet()) {
storage.storeJob(entry.getKey(), replace, jedis);
for (Trigger trigger : entry.getValue()) {
storage.storeTrigger((OperableTrigger) trigger, replace, jedis);
}
}
return null;
}
}, "Could not store jobs and triggers.");
}
/**
* Remove (delete) the {@link org.quartz.Job}
with the given
* key, and any {@link org.quartz.Trigger}
s that reference
* it.
*
*
* If removal of the Job
results in an empty group, the
* group should be removed from the JobStore
's list of
* known group names.
*
*
* @param jobKey the key of the job to be removed
* @return true
if a Job
with the given name &
* group was found and removed from the store.
*/
@Override
public boolean removeJob(final JobKey jobKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.removeJob(jobKey, jedis);
}
}, "Could not remove job.");
}
@Override
public boolean removeJobs(final List jobKeys) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
boolean removed = jobKeys.size() > 0;
for (JobKey jobKey : jobKeys) {
removed = storage.removeJob(jobKey, jedis) && removed;
}
return removed;
}
}, "Could not remove jobs.");
}
/**
* Retrieve the {@link org.quartz.JobDetail}
for the given
* {@link org.quartz.Job}
.
*
* @param jobKey the {@link org.quartz.JobKey} describing the desired job
* @return The desired Job
, or null if there is no match.
*/
@Override
public JobDetail retrieveJob(final JobKey jobKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public JobDetail doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
return storage.retrieveJob(jobKey, jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException("Error retrieving job: " + e.getMessage(), e);
}
}
}, "Could not retrieve job.");
}
/**
* Store the given {@link org.quartz.Trigger}
.
*
* @param newTrigger The Trigger
to be stored.
* @param replaceExisting If true
, any Trigger
existing in
* the JobStore
with the same name & group should
* be over-written.
* @throws org.quartz.ObjectAlreadyExistsException if a Trigger
with the same name/group already
* exists, and replaceExisting is set to false.
* @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public void storeTrigger(final OperableTrigger newTrigger, final boolean replaceExisting) throws ObjectAlreadyExistsException, JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.storeTrigger(newTrigger, replaceExisting, jedis);
return null;
}
}, "Could not store trigger.");
}
/**
* Remove (delete) the {@link org.quartz.Trigger}
with the
* given key.
*
*
* If removal of the Trigger
results in an empty group, the
* group should be removed from the JobStore
's list of
* known group names.
*
*
*
* If removal of the Trigger
results in an 'orphaned' Job
* that is not 'durable', then the Job
should be deleted
* also.
*
*
* @param triggerKey the key of the trigger to be removed
* @return true
if a Trigger
with the given
* name & group was found and removed from the store.
*/
@Override
public boolean removeTrigger(final TriggerKey triggerKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
return storage.removeTrigger(triggerKey, jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException("Error removing trigger: " + e.getMessage(), e);
}
}
}, "Could not remove trigger.");
}
@Override
public boolean removeTriggers(final List triggerKeys) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
boolean removed = triggerKeys.size() > 0;
for (TriggerKey triggerKey : triggerKeys) {
try {
removed = storage.removeTrigger(triggerKey, jedis) && removed;
} catch (ClassNotFoundException e) {
throw new JobPersistenceException(e.getMessage(), e);
}
}
return removed;
}
}, "Could not remove trigger.");
}
/**
* Remove (delete) the {@link org.quartz.Trigger}
with the
* given key, and store the new given one - which must be associated
* with the same job.
*
* @param triggerKey the key of the trigger to be replaced
* @param newTrigger The new Trigger
to be stored.
* @return true
if a Trigger
with the given
* name & group was found and removed from the store.
*/
@Override
public boolean replaceTrigger(final TriggerKey triggerKey, final OperableTrigger newTrigger) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
return storage.replaceTrigger(triggerKey, newTrigger, jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException(e.getMessage(), e);
}
}
}, "Could not remove trigger.");
}
/**
* Retrieve the given {@link org.quartz.Trigger}
.
*
* @param triggerKey the key of the desired trigger
* @return The desired Trigger
, or null if there is no
* match.
*/
@Override
public OperableTrigger retrieveTrigger(final TriggerKey triggerKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public OperableTrigger doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.retrieveTrigger(triggerKey, jedis);
}
}, "Could not retrieve trigger.");
}
/**
* Determine whether a {@link org.quartz.Job} with the given identifier already
* exists within the scheduler.
*
* @param jobKey the identifier to check for
* @return true if a Job exists with the given identifier
* @throws org.quartz.JobPersistenceException
*/
@Override
public boolean checkExists(final JobKey jobKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.checkExists(jobKey, jedis);
}
}, "Could not check if job exists: " + jobKey);
}
/**
* Determine whether a {@link org.quartz.Trigger} with the given identifier already
* exists within the scheduler.
*
* @param triggerKey the identifier to check for
* @return true if a Trigger exists with the given identifier
* @throws org.quartz.JobPersistenceException
*/
@Override
public boolean checkExists(final TriggerKey triggerKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.checkExists(triggerKey, jedis);
}
}, "Could not check if trigger exists: " + triggerKey);
}
/**
* Clear (delete!) all scheduling data - all {@link org.quartz.Job}s, {@link org.quartz.Trigger}s
* {@link org.quartz.Calendar}s.
*
* @throws org.quartz.JobPersistenceException
*/
@Override
public void clearAllSchedulingData() throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
storage.clearAllSchedulingData(jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException("Could not clear scheduling data.");
}
return null;
}
}, "Could not clear scheduling data.");
}
/**
* Store the given {@link org.quartz.Calendar}
.
*
* @param name The name of the calendar
* @param calendar The Calendar
to be stored.
* @param replaceExisting If true
, any Calendar
existing
* in the JobStore
with the same name & group
* should be over-written.
* @param updateTriggers If true
, any Trigger
s existing
* in the JobStore
that reference an existing
* Calendar with the same name with have their next fire time
* re-computed with the new Calendar
.
* @throws org.quartz.ObjectAlreadyExistsException if a Calendar
with the same name already
* exists, and replaceExisting is set to false.
*/
@Override
public void storeCalendar(final String name, final Calendar calendar, final boolean replaceExisting, final boolean updateTriggers) throws ObjectAlreadyExistsException, JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.storeCalendar(name, calendar, replaceExisting, updateTriggers, jedis);
return null;
}
}, "Could not store calendar.");
}
/**
* Remove (delete) the {@link org.quartz.Calendar}
with the
* given name.
*
*
* If removal of the Calendar
would result in
* Trigger
s pointing to non-existent calendars, then a
* JobPersistenceException
will be thrown.
* *
*
* @param calName The name of the Calendar
to be removed.
* @return true
if a Calendar
with the given name
* was found and removed from the store.
*/
@Override
public boolean removeCalendar(final String calName) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Boolean doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.removeCalendar(calName, jedis);
}
}, "Could not remove calendar.");
}
/**
* Retrieve the given {@link org.quartz.Trigger}
.
*
* @param calName The name of the Calendar
to be retrieved.
* @return The desired Calendar
, or null if there is no
* match.
*/
@Override
public Calendar retrieveCalendar(final String calName) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Calendar doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.retrieveCalendar(calName, jedis);
}
}, "Could not retrieve calendar.");
}
/**
* Get the number of {@link org.quartz.Job}
s that are
* stored in the JobsStore
.
*/
@Override
public int getNumberOfJobs() throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Integer doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getNumberOfJobs(jedis);
}
}, "Could not get number of jobs.");
}
/**
* Get the number of {@link org.quartz.Trigger}
s that are
* stored in the JobsStore
.
*/
@Override
public int getNumberOfTriggers() throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Integer doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getNumberOfTriggers(jedis);
}
}, "Could not get number of jobs.");
}
/**
* Get the number of {@link org.quartz.Calendar}
s that are
* stored in the JobsStore
.
*/
@Override
public int getNumberOfCalendars() throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Integer doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getNumberOfCalendars(jedis);
}
}, "Could not get number of jobs.");
}
/**
* Get the keys of all of the {@link org.quartz.Job}
s that
* have the given group name.
*
*
* If there are no jobs in the given group name, the result should be
* an empty collection (not null
).
*
*
* @param matcher the matcher for job key comparison
*/
@Override
public Set getJobKeys(final GroupMatcher matcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Set doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getJobKeys(matcher, jedis);
}
}, "Could not retrieve JobKeys.");
}
/**
* Get the names of all of the {@link org.quartz.Trigger}
s
* that have the given group name.
*
*
* If there are no triggers in the given group name, the result should be a
* zero-length array (not null
).
*
*
* @param matcher the matcher with which to compare trigger groups
*/
@Override
public Set getTriggerKeys(final GroupMatcher matcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Set doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getTriggerKeys(matcher, jedis);
}
}, "Could not retrieve TriggerKeys.");
}
/**
* Get the names of all of the {@link org.quartz.Job}
* groups.
*
*
* If there are no known group names, the result should be a zero-length
* array (not null
).
*
*/
@Override
public List getJobGroupNames() throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getJobGroupNames(jedis);
}
}, "Could not retrieve job group names.");
}
/**
* Get the names of all of the {@link org.quartz.Trigger}
* groups.
*
*
* If there are no known group names, the result should be a zero-length
* array (not null
).
*
*/
@Override
public List getTriggerGroupNames() throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getTriggerGroupNames(jedis);
}
}, "Could not retrieve trigger group names.");
}
/**
* Get the names of all of the {@link org.quartz.Calendar}
s
* in the JobStore
.
*
*
* If there are no Calendars in the given group name, the result should be
* a zero-length array (not null
).
*
*/
@Override
public List getCalendarNames() throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getCalendarNames(jedis);
}
}, "Could not retrieve calendar names.");
}
/**
* Get all of the Triggers that are associated to the given Job.
*
*
* If there are no matches, a zero-length array should be returned.
*
*
* @param jobKey the key of the job for which to retrieve triggers
*/
@Override
public List getTriggersForJob(final JobKey jobKey) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getTriggersForJob(jobKey, jedis);
}
}, "Could not retrieve triggers for job.");
}
/**
* Get the current state of the identified {@link org.quartz.Trigger}
.
*
* @param triggerKey the key of the trigger for which to retrieve state
* @see org.quartz.Trigger.TriggerState
*/
@Override
public Trigger.TriggerState getTriggerState(final TriggerKey triggerKey) throws JobPersistenceException {
return doWithLock(new LockCallback() {
@Override
public Trigger.TriggerState doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getTriggerState(triggerKey, jedis);
}
}, "Could not retrieve trigger state.");
}
/**
* Pause the {@link org.quartz.Trigger}
with the given key.
*
* @param triggerKey the key for the trigger to be paused
* @see #resumeTrigger(org.quartz.TriggerKey)
*/
@Override
public void pauseTrigger(final TriggerKey triggerKey) throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.pauseTrigger(triggerKey, jedis);
return null;
}
}, "Could not pause trigger.");
}
/**
* Pause all of the {@link org.quartz.Trigger}s
in the
* given group.
*
*
*
* The JobStore should "remember" that the group is paused, and impose the
* pause on any new triggers that are added to the group while the group is
* paused.
*
*
* @param matcher a trigger group matcher
*/
@Override
public Collection pauseTriggers(final GroupMatcher matcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Collection doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.pauseTriggers(matcher, jedis);
}
}, "Could not pause triggers.");
}
/**
* Pause the {@link org.quartz.Job}
with the given name - by
* pausing all of its current Trigger
s.
*
* @param jobKey the key of the job to be paused
* @see #resumeJob(org.quartz.JobKey)
*/
@Override
public void pauseJob(final JobKey jobKey) throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.pauseJob(jobKey, jedis);
return null;
}
}, "Could not pause job.");
}
/**
* Pause all of the {@link org.quartz.Job}s
in the given
* group - by pausing all of their Trigger
s.
*
*
* The JobStore should "remember" that the group is paused, and impose the
* pause on any new jobs that are added to the group while the group is
* paused.
*
*
* @param groupMatcher the mather which will determine which job group should be paused
* @see #resumeJobs(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public Collection pauseJobs(final GroupMatcher groupMatcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Collection doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.pauseJobs(groupMatcher, jedis);
}
}, "Could not pause jobs.");
}
/**
* Resume (un-pause) the {@link org.quartz.Trigger}
with the
* given key.
*
*
* If the Trigger
missed one or more fire-times, then the
* Trigger
's misfire instruction will be applied.
*
*
* @param triggerKey the key of the trigger to be resumed
* @see #pauseTrigger(org.quartz.TriggerKey)
*/
@Override
public void resumeTrigger(final TriggerKey triggerKey) throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.resumeTrigger(triggerKey, jedis);
return null;
}
}, "Could not resume trigger.");
}
/**
* Resume (un-pause) all of the {@link org.quartz.Trigger}s
* in the given group.
*
*
* If any Trigger
missed one or more fire-times, then the
* Trigger
's misfire instruction will be applied.
*
*
* @param matcher a trigger group matcher
* @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public Collection resumeTriggers(final GroupMatcher matcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Collection doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.resumeTriggers(matcher, jedis);
}
}, "Could not resume trigger group(s).");
}
@Override
public Set getPausedTriggerGroups() throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Set doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.getPausedTriggerGroups(jedis);
}
}, "Could not retrieve paused trigger groups.");
}
/**
* Resume (un-pause) the {@link org.quartz.Job}
with the
* given key.
*
*
* If any of the Job
'sTrigger
s missed one
* or more fire-times, then the Trigger
's misfire
* instruction will be applied.
*
*
* @param jobKey the key of the job to be resumed
* @see #pauseJob(org.quartz.JobKey)
*/
@Override
public void resumeJob(final JobKey jobKey) throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.resumeJob(jobKey, jedis);
return null;
}
}, "Could not resume job.");
}
/**
* Resume (un-pause) all of the {@link org.quartz.Job}s
in
* the given group.
*
*
* If any of the Job
s had Trigger
s that
* missed one or more fire-times, then the Trigger
's
* misfire instruction will be applied.
*
*
* @param matcher the matcher for job group name comparison
* @see #pauseJobs(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public Collection resumeJobs(final GroupMatcher matcher) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public Collection doWithLock(JedisCommands jedis) throws JobPersistenceException {
return storage.resumeJobs(matcher, jedis);
}
}, "Could not resume jobs.");
}
/**
* Pause all triggers - equivalent of calling pauseTriggerGroup(group)
* on every group.
*
*
* When resumeAll()
is called (to un-pause), trigger misfire
* instructions WILL be applied.
*
*
* @see #resumeAll()
* @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public void pauseAll() throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.pauseAll(jedis);
return null;
}
}, "Could not pause all triggers.");
}
/**
* Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group)
* on every group.
*
*
* If any Trigger
missed one or more fire-times, then the
* Trigger
's misfire instruction will be applied.
*
*
* @see #pauseAll()
*/
@Override
public void resumeAll() throws JobPersistenceException {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.resumeAll(jedis);
return null;
}
}, "Could not resume all triggers.");
}
/**
* Get a handle to the next trigger to be fired, and mark it as 'reserved'
* by the calling scheduler.
*
* @param noLaterThan If > 0, the JobStore should only return a Trigger
* that will fire no later than the time represented in this value as
* milliseconds.
* @param maxCount the maximum number of triggers to return
* @param timeWindow @see #releaseAcquiredTrigger(Trigger)
*/
@Override
public List acquireNextTriggers(final long noLaterThan, final int maxCount, final long timeWindow) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
return storage.acquireNextTriggers(noLaterThan, maxCount, timeWindow, jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException(e.getMessage(), e);
}
}
}, "Could not acquire next triggers.");
}
/**
* Inform the JobStore
that the scheduler no longer plans to
* fire the given Trigger
, that it had previously acquired
* (reserved).
*
* @param trigger the trigger to be released
*/
@Override
public void releaseAcquiredTrigger(final OperableTrigger trigger) {
try {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
storage.releaseAcquiredTrigger(trigger, jedis);
return null;
}
}, "Could not release acquired trigger.");
} catch (JobPersistenceException e) {
logger.error("Could not release acquired trigger.", e);
}
}
/**
* Inform the JobStore
that the scheduler is now firing the
* given Trigger
(executing its associated Job
),
* that it had previously acquired (reserved).
*
* @param triggers a list of triggers which are being fired by the scheduler
* @return may return null if all the triggers or their calendars no longer exist, or
* if the trigger was not successfully put into the 'executing'
* state. Preference is to return an empty list if none of the triggers
* could be fired.
*/
@Override
public List triggersFired(final List triggers) throws JobPersistenceException {
return doWithLock(new LockCallback>() {
@Override
public List doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
return storage.triggersFired(triggers, jedis);
} catch (ClassNotFoundException e) {
throw new JobPersistenceException(e.getMessage(), e);
}
}
}, "Could not set triggers as fired.");
}
/**
* Inform the JobStore
that the scheduler has completed the
* firing of the given Trigger
(and the execution of its
* associated Job
completed, threw an exception, or was vetoed),
* and that the {@link org.quartz.JobDataMap}
* in the given JobDetail
should be updated if the Job
* is stateful.
*
* @param trigger the completetd trigger
* @param jobDetail the completed job
* @param triggerInstCode the trigger completion code
*/
@Override
public void triggeredJobComplete(final OperableTrigger trigger, final JobDetail jobDetail, final Trigger.CompletedExecutionInstruction triggerInstCode) {
try {
doWithLock(new LockCallbackWithoutResult() {
@Override
public Void doWithLock(JedisCommands jedis) throws JobPersistenceException {
try {
storage.triggeredJobComplete(trigger, jobDetail, triggerInstCode, jedis);
} catch (ClassNotFoundException e) {
logger.error("Could not handle job completion.", e);
}
return null;
}
});
} catch (JobPersistenceException e) {
logger.error("Could not handle job completion.", e);
}
}
/**
* Perform Redis operations while possessing lock
* @param callback operation(s) to be performed during lock
* @param return type
* @return response from callback, if any
* @throws JobPersistenceException
*/
protected T doWithLock(LockCallback callback) throws JobPersistenceException {
return doWithLock(callback, null);
}
/**
* Perform a redis operation while lock is acquired
* @param callback a callback containing the actions to perform during lock
* @param errorMessage optional error message to include in exception should an error arise
* @param return class
* @return the result of the actions performed while locked, if any
* @throws JobPersistenceException
*/
protected T doWithLock(LockCallback callback, String errorMessage) throws JobPersistenceException {
JedisCommands jedis = null;
try {
jedis = getResource();
try {
storage.waitForLock(jedis);
return callback.doWithLock(jedis);
} catch (ObjectAlreadyExistsException e) {
throw e;
} catch (Exception e) {
if (errorMessage == null || errorMessage.isEmpty()) {
errorMessage = "Job storage error.";
}
throw new JobPersistenceException(errorMessage, e);
} finally {
storage.unlock(jedis);
}
} finally {
if (jedis != null && jedis instanceof Jedis) {
// only close if we're not using a JedisCluster instance
((Jedis) jedis).close();
}
}
}
private JedisCommands getResource() throws JobPersistenceException {
if (jedisCluster != null) {
return jedisCluster;
} else {
return jedisPool.getResource();
}
}
protected interface LockCallback {
T doWithLock(JedisCommands jedis) throws JobPersistenceException;
}
private abstract class LockCallbackWithoutResult implements LockCallback {}
public void setLockTimeout(int lockTimeout) {
this.lockTimeout = lockTimeout;
}
public void setLockTimeout(String lockTimeout){
setLockTimeout(Integer.valueOf(lockTimeout));
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public void setPort(String port){
setPort(Integer.valueOf(port));
}
public void setDatabase(int database){
this.database = (short) database;
}
public void setDatabase(String database){
setDatabase(Short.valueOf(database));
}
public void setSsl(boolean ssl){
this.ssl = ssl;
}
public void setSsl(String ssl){
setSsl(Boolean.valueOf(ssl));
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
public void setKeyDelimiter(String keyDelimiter) {
this.keyDelimiter = keyDelimiter;
}
public void setRedisCluster(boolean clustered) {
this.redisCluster = clustered;
}
public void setRedisSentinel(boolean sentinel) {
this.redisSentinel = sentinel;
}
public void setMasterGroupName(String masterGroupName) {
this.masterGroupName = masterGroupName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* Inform the JobStore
of the Scheduler instance's Id,
* prior to initialize being invoked.
*
* @param schedInstId the instanceid for the current scheduler
* @since 1.7
*/
@Override
public void setInstanceId(String schedInstId) {
instanceId = schedInstId;
}
/**
* Inform the JobStore
of the Scheduler instance's name,
* prior to initialize being invoked.
*
* @param schedName the name of the current scheduler
* @since 1.7
*/
@Override
public void setInstanceName(String schedName) {
// nothing to do
}
/**
* Tells the JobStore the pool size used to execute jobs
*
* @param poolSize amount of threads allocated for job execution
* @since 2.0
*/
@Override
public void setThreadPoolSize(int poolSize) {
// nothing to do
}
private Set buildNodesSetFromHost() {
Set nodes = new HashSet<>();
for (String hostName : host.split(",")) {
int hostPort = port;
if (hostName.contains(":")) {
String[] parts = hostName.split(":");
hostName = parts[0];
hostPort = Integer.valueOf(parts[1]);
}
nodes.add(new HostAndPort(hostName, hostPort));
}
return nodes;
}
}