All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.joelinn.quartz.jobstore.RedisJobStore Maven / Gradle / Ivy

The newest version!
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 Triggers 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 * Triggers 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 Triggers. * * @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 Triggers. *

*

* 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy