org.quartz.simpl.RAMJobStore Maven / Gradle / Ivy
Show all versions of sundial Show documentation
/*
* Copyright 2001-2009 Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.quartz.simpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
import org.quartz.Calendar;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.Trigger.TriggerState;
import org.quartz.Trigger.TriggerTimeComparator;
import org.quartz.TriggerKey;
import org.quartz.exceptions.JobPersistenceException;
import org.quartz.exceptions.ObjectAlreadyExistsException;
import org.quartz.exceptions.SchedulerException;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.matchers.StringMatcher;
import org.quartz.spi.JobStore;
import org.quartz.spi.OperableTrigger;
import org.quartz.spi.SchedulerSignaler;
import org.quartz.spi.TriggerFiredBundle;
import org.quartz.spi.TriggerFiredResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* This class implements a {@link org.quartz.spi.JobStore}
that utilizes RAM as its storage device.
*
*
* As you should know, the ramification of this is that access is extrememly fast, but the data is completely volatile - therefore this JobStore
should not be used if true persistence between program shutdowns is required.
*
*
* @author James House
* @author Sharada Jambula
* @author Eric Mueller
*/
public class RAMJobStore implements JobStore {
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Data members. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
private HashMap jobsByKey = new HashMap(1000);
private HashMap triggersByKey = new HashMap(1000);
private HashMap> jobsByGroup = new HashMap>(25);
private HashMap> triggersByGroup = new HashMap>(25);
private TreeSet timeTriggers = new TreeSet(new TriggerWrapperComparator());
private HashMap calendarsByName = new HashMap(25);
private ArrayList triggers = new ArrayList(1000);
private final Object lock = new Object();
private HashSet pausedTriggerGroups = new HashSet();
private HashSet pausedJobGroups = new HashSet();
private HashSet blockedJobs = new HashSet();
private long misfireThreshold = 5000l;
private SchedulerSignaler mSignaler;
private final Logger logger = LoggerFactory.getLogger(getClass());
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constructors. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
* Create a new RAMJobStore
.
*
*/
public RAMJobStore() {
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Interface. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
* Called by the QuartzScheduler before the JobStore
is used, in order to give the it a chance to initialize.
*
*/
@Override
public void initialize(SchedulerSignaler signaler) {
mSignaler = signaler;
logger.info("RAMJobStore initialized.");
}
@Override
public void schedulerStarted() throws SchedulerException {
// nothing to do
}
public long getMisfireThreshold() {
return misfireThreshold;
}
/**
* The number of milliseconds by which a trigger must have missed its next-fire-time, in order for it to be considered "misfired" and thus have its misfire instruction applied.
*
* @param misfireThreshold
*/
public void setMisfireThreshold(long misfireThreshold) {
if (misfireThreshold < 1) {
throw new IllegalArgumentException("Misfirethreashold must be larger than 0");
}
this.misfireThreshold = misfireThreshold;
}
/**
*
* 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() {
}
@Override
public boolean supportsPersistence() {
return false;
}
/**
* Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s {@link Calendar}s.
*
* @throws JobPersistenceException
*/
@Override
public void clearAllSchedulingData() throws JobPersistenceException {
synchronized (lock) {
// unschedule jobs (delete triggers)
List lst = getTriggerGroupNames();
for (String group : lst) {
Set keys = getTriggerKeys(GroupMatcher.groupEquals(group));
for (TriggerKey key : keys) {
removeTrigger(key);
}
}
// delete jobs
lst = getJobGroupNames();
for (String group : lst) {
Set keys = getJobKeys(GroupMatcher.groupEquals(group));
for (JobKey key : keys) {
removeJob(key);
}
}
// delete calendars
lst = getCalendarNames();
for (String name : lst) {
removeCalendar(name);
}
}
}
/**
*
* 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 ObjectAlreadyExistsException if a Job
with the same name/group already exists.
*/
@Override
public void storeJobAndTrigger(JobDetail newJob, OperableTrigger newTrigger) throws JobPersistenceException {
storeJob(newJob, false);
storeTrigger(newTrigger, false);
}
/**
*
* Store the given {@link org.quartz.Job}
.
*
*
* @param newJob The Job
to be stored.
* @param replaceExisting If true
, any Job
existing in the JobStore
with the same name & group should be over-written.
* @throws ObjectAlreadyExistsException if a Job
with the same name/group already exists, and replaceExisting is set to false.
*/
@Override
public void storeJob(JobDetail newJob, boolean replaceExisting) throws ObjectAlreadyExistsException {
JobWrapper jw = new JobWrapper((JobDetail) newJob.clone());
boolean repl = false;
synchronized (lock) {
if (jobsByKey.get(jw.key) != null) {
if (!replaceExisting) {
throw new ObjectAlreadyExistsException(newJob);
}
repl = true;
}
if (!repl) {
// get job group
HashMap grpMap = jobsByGroup.get(newJob.getKey().getGroup());
if (grpMap == null) {
grpMap = new HashMap(100);
jobsByGroup.put(newJob.getKey().getGroup(), grpMap);
}
// add to jobs by group
grpMap.put(newJob.getKey(), jw);
// add to jobs by FQN map
jobsByKey.put(jw.key, jw);
} else {
// update job detail
JobWrapper orig = jobsByKey.get(jw.key);
orig.jobDetail = jw.jobDetail; // already cloned
}
}
}
/**
*
* Remove (delete) the {@link org.quartz.Job}
with the given name, and any {@link org.quartz.Trigger}
s that reference it.
*
*
* @return true
if a Job
with the given name & group was found and removed from the store.
*/
@Override
public boolean removeJob(JobKey jobKey) {
boolean found = false;
synchronized (lock) {
List triggers = getTriggersForJob(jobKey);
for (OperableTrigger trig : triggers) {
this.removeTrigger(trig.getKey());
found = true;
}
found = (jobsByKey.remove(jobKey) != null) | found;
if (found) {
HashMap grpMap = jobsByGroup.get(jobKey.getGroup());
if (grpMap != null) {
grpMap.remove(jobKey);
if (grpMap.size() == 0) {
jobsByGroup.remove(jobKey.getGroup());
}
}
}
}
return found;
}
@Override
public boolean removeJobs(List jobKeys) throws JobPersistenceException {
boolean allFound = true;
synchronized (lock) {
for (JobKey key : jobKeys) {
allFound = removeJob(key) && allFound;
}
}
return allFound;
}
@Override
public boolean removeTriggers(List triggerKeys) throws JobPersistenceException {
boolean allFound = true;
synchronized (lock) {
for (TriggerKey key : triggerKeys) {
allFound = removeTrigger(key) && allFound;
}
}
return allFound;
}
@Override
public void storeJobsAndTriggers(Map> triggersAndJobs, boolean replace) throws ObjectAlreadyExistsException, JobPersistenceException {
synchronized (lock) {
// make sure there are no collisions...
if (!replace) {
for (JobDetail job : triggersAndJobs.keySet()) {
if (checkExists(job.getKey())) {
throw new ObjectAlreadyExistsException(job);
}
for (Trigger trigger : triggersAndJobs.get(job)) {
if (checkExists(trigger.getKey())) {
throw new ObjectAlreadyExistsException(trigger);
}
}
}
}
// do bulk add...
for (JobDetail job : triggersAndJobs.keySet()) {
storeJob(job, true);
for (Trigger trigger : triggersAndJobs.get(job)) {
storeTrigger((OperableTrigger) trigger, true);
}
}
}
}
/**
*
* 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 ObjectAlreadyExistsException if a Trigger
with the same name/group already exists, and replaceExisting is set to false.
* @see #pauseTriggerGroup(SchedulingContext, String)
*/
@Override
public void storeTrigger(OperableTrigger newTrigger, boolean replaceExisting) throws JobPersistenceException {
TriggerWrapper tw = new TriggerWrapper((OperableTrigger) newTrigger.clone());
synchronized (lock) {
if (triggersByKey.get(tw.key) != null) {
if (!replaceExisting) {
throw new ObjectAlreadyExistsException(newTrigger);
}
removeTrigger(newTrigger.getKey(), false);
}
if (retrieveJob(newTrigger.getJobKey()) == null) {
throw new JobPersistenceException("The job (" + newTrigger.getJobKey() + ") referenced by the trigger does not exist.");
}
// add to triggers array
triggers.add(tw);
// add to triggers by group
HashMap grpMap = triggersByGroup.get(newTrigger.getKey().getGroup());
if (grpMap == null) {
grpMap = new HashMap(100);
triggersByGroup.put(newTrigger.getKey().getGroup(), grpMap);
}
grpMap.put(newTrigger.getKey(), tw);
// add to triggers by FQN map
triggersByKey.put(tw.key, tw);
if (pausedTriggerGroups.contains(newTrigger.getKey().getGroup()) || pausedJobGroups.contains(newTrigger.getJobKey().getGroup())) {
tw.state = TriggerWrapper.STATE_PAUSED;
if (blockedJobs.contains(tw.jobKey)) {
tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED;
}
} else if (blockedJobs.contains(tw.jobKey)) {
tw.state = TriggerWrapper.STATE_BLOCKED;
} else {
timeTriggers.add(tw);
}
}
}
/**
*
* Remove (delete) the {@link org.quartz.Trigger}
with the given name.
*
*
* @return true
if a Trigger
with the given name & group was found and removed from the store.
*/
@Override
public boolean removeTrigger(TriggerKey triggerKey) {
return removeTrigger(triggerKey, true);
}
private boolean removeTrigger(TriggerKey key, boolean removeOrphanedJob) {
boolean found = false;
synchronized (lock) {
// remove from triggers by FQN map
found = (triggersByKey.remove(key) == null) ? false : true;
if (found) {
TriggerWrapper tw = null;
// remove from triggers by group
HashMap grpMap = triggersByGroup.get(key.getGroup());
if (grpMap != null) {
grpMap.remove(key);
if (grpMap.size() == 0) {
triggersByGroup.remove(key.getGroup());
}
}
// remove from triggers array
Iterator tgs = triggers.iterator();
while (tgs.hasNext()) {
tw = (TriggerWrapper) tgs.next();
if (key.equals(tw.key)) {
tgs.remove();
break;
}
}
timeTriggers.remove(tw);
if (removeOrphanedJob) {
JobWrapper jw = jobsByKey.get(tw.jobKey);
List trigs = getTriggersForJob(tw.jobKey);
if ((trigs == null || trigs.size() == 0) && !jw.jobDetail.isDurable()) {
if (removeJob(jw.key)) {
mSignaler.notifySchedulerListenersJobDeleted(jw.key);
}
}
}
}
}
return found;
}
/**
* @see org.quartz.spi.JobStore#replaceTrigger(org.quartz.core.SchedulingContext, java.lang.String, java.lang.String, org.quartz.Trigger)
*/
@Override
public boolean replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) throws JobPersistenceException {
boolean found = false;
synchronized (lock) {
// remove from triggers by FQN map
TriggerWrapper tw = triggersByKey.remove(triggerKey);
found = (tw == null) ? false : true;
if (found) {
if (!tw.getTrigger().getJobKey().equals(newTrigger.getJobKey())) {
throw new JobPersistenceException("New trigger is not related to the same job as the old trigger.");
}
tw = null;
// remove from triggers by group
HashMap grpMap = triggersByGroup.get(triggerKey.getGroup());
if (grpMap != null) {
grpMap.remove(triggerKey);
if (grpMap.size() == 0) {
triggersByGroup.remove(triggerKey.getGroup());
}
}
// remove from triggers array
Iterator tgs = triggers.iterator();
while (tgs.hasNext()) {
tw = (TriggerWrapper) tgs.next();
if (triggerKey.equals(tw.key)) {
tgs.remove();
break;
}
}
timeTriggers.remove(tw);
try {
storeTrigger(newTrigger, false);
} catch (JobPersistenceException jpe) {
storeTrigger(tw.getTrigger(), false); // put previous trigger back...
throw jpe;
}
}
}
return found;
}
/**
*
* Retrieve the {@link org.quartz.JobDetail}
for the given {@link org.quartz.Job}
.
*
*
* @return The desired Job
, or null if there is no match.
*/
@Override
public JobDetail retrieveJob(JobKey jobKey) {
synchronized (lock) {
JobWrapper jw = jobsByKey.get(jobKey);
return (jw != null) ? (JobDetail) jw.jobDetail.clone() : null;
}
}
/**
*
* Retrieve the given {@link org.quartz.Trigger}
.
*
*
* @return The desired Trigger
, or null if there is no match.
*/
@Override
public OperableTrigger retrieveTrigger(TriggerKey triggerKey) {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
return (tw != null) ? (OperableTrigger) tw.getTrigger().clone() : null;
}
}
/**
* Determine whether a {@link 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 SchedulerException
*/
@Override
public boolean checkExists(JobKey jobKey) {
synchronized (lock) {
JobWrapper jw = jobsByKey.get(jobKey);
return (jw != null);
}
}
/**
* Determine whether a {@link 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 SchedulerException
*/
@Override
public boolean checkExists(TriggerKey triggerKey) {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
return (tw != null);
}
}
/**
*
* Get the current state of the identified {@link Trigger}
.
*
*
* @see Trigger#NORMAL
* @see Trigger#PAUSED
* @see Trigger#COMPLETE
* @see Trigger#ERROR
* @see Trigger#BLOCKED
* @see Trigger#NONE
*/
@Override
public TriggerState getTriggerState(TriggerKey triggerKey) throws JobPersistenceException {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
if (tw == null) {
return TriggerState.NONE;
}
if (tw.state == TriggerWrapper.STATE_COMPLETE) {
return TriggerState.COMPLETE;
}
if (tw.state == TriggerWrapper.STATE_PAUSED) {
return TriggerState.PAUSED;
}
if (tw.state == TriggerWrapper.STATE_PAUSED_BLOCKED) {
return TriggerState.PAUSED;
}
if (tw.state == TriggerWrapper.STATE_BLOCKED) {
return TriggerState.BLOCKED;
}
if (tw.state == TriggerWrapper.STATE_ERROR) {
return TriggerState.ERROR;
}
return TriggerState.NORMAL;
}
}
/**
*
* Store the given {@link org.quartz.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 ObjectAlreadyExistsException if a Calendar
with the same name already exists, and replaceExisting is set to false.
*/
@Override
public void storeCalendar(String name, Calendar calendar, boolean replaceExisting, boolean updateTriggers) throws ObjectAlreadyExistsException {
calendar = (Calendar) calendar.clone();
synchronized (lock) {
Object obj = calendarsByName.get(name);
if (obj != null && replaceExisting == false) {
throw new ObjectAlreadyExistsException("Calendar with name '" + name + "' already exists.");
} else if (obj != null) {
calendarsByName.remove(name);
}
calendarsByName.put(name, calendar);
if (obj != null && updateTriggers) {
Iterator trigs = getTriggerWrappersForCalendar(name).iterator();
while (trigs.hasNext()) {
TriggerWrapper tw = (TriggerWrapper) trigs.next();
OperableTrigger trig = tw.getTrigger();
boolean removed = timeTriggers.remove(tw);
trig.updateWithNewCalendar(calendar, getMisfireThreshold());
if (removed) {
timeTriggers.add(tw);
}
}
}
}
}
/**
*
* 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(String calName) throws JobPersistenceException {
int numRefs = 0;
synchronized (lock) {
Iterator itr = triggers.iterator();
while (itr.hasNext()) {
OperableTrigger trigg = ((TriggerWrapper) itr.next()).trigger;
if (trigg.getCalendarName() != null && trigg.getCalendarName().equals(calName)) {
numRefs++;
}
}
}
if (numRefs > 0) {
throw new JobPersistenceException("Calender cannot be removed if it referenced by a Trigger!");
}
return (calendarsByName.remove(calName) != null);
}
/**
*
* 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(String calName) {
synchronized (lock) {
Calendar cal = calendarsByName.get(calName);
if (cal != null) {
return (Calendar) cal.clone();
}
return null;
}
}
/**
*
* Get the number of {@link org.quartz.JobDetail}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfJobs() {
synchronized (lock) {
return jobsByKey.size();
}
}
/**
*
* Get the number of {@link org.quartz.Trigger}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfTriggers() {
synchronized (lock) {
return triggers.size();
}
}
/**
*
* Get the number of {@link org.quartz.Calendar}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfCalendars() {
synchronized (lock) {
return calendarsByName.size();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Job}
s that match the given groupMatcher.
*
*/
@Override
public Set getJobKeys(GroupMatcher matcher) {
Set outList = null;
synchronized (lock) {
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
String compareToValue = matcher.getCompareToValue();
switch (operator) {
case EQUALS:
HashMap grpMap = jobsByGroup.get(compareToValue);
if (grpMap != null) {
outList = new HashSet();
for (JobWrapper jw : grpMap.values()) {
if (jw != null) {
outList.add(jw.jobDetail.getKey());
}
}
}
break;
default:
for (Map.Entry> entry : jobsByGroup.entrySet()) {
if (operator.evaluate(entry.getKey(), compareToValue) && entry.getValue() != null) {
if (outList == null) {
outList = new HashSet();
}
for (JobWrapper jobWrapper : entry.getValue().values()) {
if (jobWrapper != null) {
outList.add(jobWrapper.jobDetail.getKey());
}
}
}
}
}
}
return outList == null ? java.util.Collections. emptySet() : outList;
}
/**
*
* 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() {
synchronized (lock) {
return new LinkedList(calendarsByName.keySet());
}
}
/**
*
* Get the names of all of the {@link org.quartz.Trigger}
s that match the given groupMatcher.
*
*/
@Override
public Set getTriggerKeys(GroupMatcher matcher) {
Set outList = null;
synchronized (lock) {
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
String compareToValue = matcher.getCompareToValue();
switch (operator) {
case EQUALS:
HashMap grpMap = triggersByGroup.get(compareToValue);
if (grpMap != null) {
outList = new HashSet();
for (TriggerWrapper tw : grpMap.values()) {
if (tw != null) {
outList.add(tw.trigger.getKey());
}
}
}
break;
default:
for (Map.Entry> entry : triggersByGroup.entrySet()) {
if (operator.evaluate(entry.getKey(), compareToValue) && entry.getValue() != null) {
if (outList == null) {
outList = new HashSet();
}
for (TriggerWrapper triggerWrapper : entry.getValue().values()) {
if (triggerWrapper != null) {
outList.add(triggerWrapper.trigger.getKey());
}
}
}
}
}
}
return outList == null ? Collections. emptySet() : outList;
}
/**
*
* Get the names of all of the {@link org.quartz.Job}
groups.
*
*/
@Override
public List getJobGroupNames() {
List outList = null;
synchronized (lock) {
outList = new LinkedList(jobsByGroup.keySet());
}
return outList;
}
/**
*
* Get the names of all of the {@link org.quartz.Trigger}
groups.
*
*/
@Override
public List getTriggerGroupNames() {
LinkedList outList = null;
synchronized (lock) {
outList = new LinkedList(triggersByGroup.keySet());
}
return outList;
}
/**
*
* Get all of the Triggers that are associated to the given Job.
*
*
* If there are no matches, a zero-length array should be returned.
*
*/
@Override
public List getTriggersForJob(JobKey jobKey) {
ArrayList trigList = new ArrayList();
synchronized (lock) {
for (int i = 0; i < triggers.size(); i++) {
TriggerWrapper tw = triggers.get(i);
if (tw.jobKey.equals(jobKey)) {
trigList.add((OperableTrigger) tw.trigger.clone());
}
}
}
return trigList;
}
protected ArrayList getTriggerWrappersForJob(JobKey jobKey) {
ArrayList trigList = new ArrayList();
synchronized (lock) {
for (int i = 0; i < triggers.size(); i++) {
TriggerWrapper tw = triggers.get(i);
if (tw.jobKey.equals(jobKey)) {
trigList.add(tw);
}
}
}
return trigList;
}
protected ArrayList getTriggerWrappersForCalendar(String calName) {
ArrayList trigList = new ArrayList();
synchronized (lock) {
for (int i = 0; i < triggers.size(); i++) {
TriggerWrapper tw = triggers.get(i);
String tcalName = tw.getTrigger().getCalendarName();
if (tcalName != null && tcalName.equals(calName)) {
trigList.add(tw);
}
}
}
return trigList;
}
/**
*
* Pause the {@link Trigger}
with the given name.
*
*/
@Override
public void pauseTrigger(TriggerKey triggerKey) {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
// does the trigger exist?
if (tw == null || tw.trigger == null) {
return;
}
// if the trigger is "complete" pausing it does not make sense...
if (tw.state == TriggerWrapper.STATE_COMPLETE) {
return;
}
if (tw.state == TriggerWrapper.STATE_BLOCKED) {
tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED;
} else {
tw.state = TriggerWrapper.STATE_PAUSED;
}
timeTriggers.remove(tw);
}
}
/**
*
* Pause all of the known {@link Trigger}s
matching.
*
*
* The JobStore should "remember" the groups paused, and impose the pause on any new triggers that are added to one of these groups while the group is paused.
*
*/
@Override
public List pauseTriggers(GroupMatcher matcher) {
List pausedGroups;
synchronized (lock) {
pausedGroups = new LinkedList();
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
switch (operator) {
case EQUALS:
if (pausedTriggerGroups.add(matcher.getCompareToValue())) {
pausedGroups.add(matcher.getCompareToValue());
}
break;
default:
for (String group : triggersByGroup.keySet()) {
if (operator.evaluate(group, matcher.getCompareToValue())) {
if (pausedTriggerGroups.add(matcher.getCompareToValue())) {
pausedGroups.add(group);
}
}
}
}
for (String pausedGroup : pausedGroups) {
Set keys = getTriggerKeys(GroupMatcher.groupEquals(pausedGroup));
for (TriggerKey key : keys) {
pauseTrigger(key);
}
}
}
return pausedGroups;
}
/**
*
* Pause the {@link org.quartz.JobDetail}
with the given name - by pausing all of its current Trigger
s.
*
*/
@Override
public void pauseJob(JobKey jobKey) {
synchronized (lock) {
List triggers = getTriggersForJob(jobKey);
for (OperableTrigger trigger : triggers) {
pauseTrigger(trigger.getKey());
}
}
}
/**
*
* Pause all of the {@link org.quartz.JobDetail}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.
*
*/
@Override
public List pauseJobs(GroupMatcher matcher) {
List pausedGroups = new LinkedList();
synchronized (lock) {
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
switch (operator) {
case EQUALS:
if (pausedJobGroups.add(matcher.getCompareToValue())) {
pausedGroups.add(matcher.getCompareToValue());
}
break;
default:
for (String group : jobsByGroup.keySet()) {
if (operator.evaluate(group, matcher.getCompareToValue())) {
if (pausedJobGroups.add(group)) {
pausedGroups.add(group);
}
}
}
}
for (String groupName : pausedGroups) {
for (JobKey jobKey : getJobKeys(GroupMatcher.groupEquals(groupName))) {
List triggers = getTriggersForJob(jobKey);
for (OperableTrigger trigger : triggers) {
pauseTrigger(trigger.getKey());
}
}
}
}
return pausedGroups;
}
/**
*
* Resume (un-pause) the {@link Trigger}
with the given key.
*
*
* If the Trigger
missed one or more fire-times, then the Trigger
's misfire instruction will be applied.
*
*/
@Override
public void resumeTrigger(TriggerKey triggerKey) {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
// does the trigger exist?
if (tw == null || tw.trigger == null) {
return;
}
OperableTrigger trig = tw.getTrigger();
// if the trigger is not paused resuming it does not make sense...
if (tw.state != TriggerWrapper.STATE_PAUSED && tw.state != TriggerWrapper.STATE_PAUSED_BLOCKED) {
return;
}
if (blockedJobs.contains(trig.getJobKey())) {
tw.state = TriggerWrapper.STATE_BLOCKED;
} else {
tw.state = TriggerWrapper.STATE_WAITING;
}
applyMisfire(tw);
if (tw.state == TriggerWrapper.STATE_WAITING) {
timeTriggers.add(tw);
}
}
}
/**
*
* Resume (un-pause) all of the {@link Trigger}s
in the given group.
*
*
* If any Trigger
missed one or more fire-times, then the Trigger
's misfire instruction will be applied.
*
*/
@Override
public List resumeTriggers(GroupMatcher matcher) {
Set groups = new HashSet();
synchronized (lock) {
Set keys = getTriggerKeys(matcher);
for (TriggerKey triggerKey : keys) {
groups.add(triggerKey.getGroup());
if (triggersByKey.get(triggerKey) != null) {
String jobGroup = triggersByKey.get(triggerKey).jobKey.getGroup();
if (pausedJobGroups.contains(jobGroup)) {
continue;
}
}
resumeTrigger(triggerKey);
}
for (String group : groups) {
pausedTriggerGroups.remove(group);
}
}
return new ArrayList(groups);
}
/**
*
* Resume (un-pause) the {@link org.quartz.JobDetail}
with the given name.
*
*
* If any of the Job
'sTrigger
s missed one or more fire-times, then the Trigger
's misfire instruction will be applied.
*
*/
@Override
public void resumeJob(JobKey jobKey) {
synchronized (lock) {
List triggers = getTriggersForJob(jobKey);
for (OperableTrigger trigger : triggers) {
resumeTrigger(trigger.getKey());
}
}
}
/**
*
* Resume (un-pause) all of the {@link org.quartz.JobDetail}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.
*
*/
@Override
public Collection resumeJobs(GroupMatcher matcher) {
Set resumedGroups = new HashSet();
synchronized (lock) {
Set keys = getJobKeys(matcher);
for (String pausedJobGroup : pausedJobGroups) {
if (matcher.getCompareWithOperator().evaluate(pausedJobGroup, matcher.getCompareToValue())) {
resumedGroups.add(pausedJobGroup);
}
}
for (String resumedGroup : resumedGroups) {
pausedJobGroups.remove(resumedGroup);
}
for (JobKey key : keys) {
List triggers = getTriggersForJob(key);
for (OperableTrigger trigger : triggers) {
resumeTrigger(trigger.getKey());
}
}
}
return resumedGroups;
}
/**
*
* 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(SchedulingContext)
* @see #pauseTriggerGroup(SchedulingContext, String)
*/
@Override
public void pauseAll() {
synchronized (lock) {
List names = getTriggerGroupNames();
for (String name : names) {
pauseTriggers(GroupMatcher.groupEquals(name));
}
}
}
/**
*
* 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(SchedulingContext)
*/
@Override
public void resumeAll() {
synchronized (lock) {
// TODO need a match all here!
pausedJobGroups.clear();
List names = getTriggerGroupNames();
for (String name : names) {
resumeTriggers(GroupMatcher.groupEquals(name));
}
}
}
protected boolean applyMisfire(TriggerWrapper tw) {
long misfireTime = System.currentTimeMillis();
if (getMisfireThreshold() > 0) {
misfireTime -= getMisfireThreshold();
}
Date tnft = tw.trigger.getNextFireTime();
if (tnft == null || tnft.getTime() > misfireTime || tw.trigger.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) {
return false;
}
Calendar cal = null;
if (tw.trigger.getCalendarName() != null) {
cal = retrieveCalendar(tw.trigger.getCalendarName());
}
mSignaler.notifyTriggerListenersMisfired((OperableTrigger) tw.trigger.clone());
tw.trigger.updateAfterMisfire(cal);
if (tw.trigger.getNextFireTime() == null) {
tw.state = TriggerWrapper.STATE_COMPLETE;
mSignaler.notifySchedulerListenersFinalized(tw.trigger);
synchronized (lock) {
timeTriggers.remove(tw);
}
} else if (tnft.equals(tw.trigger.getNextFireTime())) {
return false;
}
return true;
}
private static final AtomicLong ftrCtr = new AtomicLong(System.currentTimeMillis());
protected String getFiredTriggerRecordId() {
return String.valueOf(ftrCtr.incrementAndGet());
}
/**
*
* Get a handle to the next trigger to be fired, and mark it as 'reserved' by the calling scheduler.
*
*
* @see #releaseAcquiredTrigger(SchedulingContext, Trigger)
*/
@Override
public List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) {
synchronized (lock) {
List result = new ArrayList();
while (true) {
TriggerWrapper tw;
try {
tw = timeTriggers.first();
if (tw == null) {
return result;
}
timeTriggers.remove(tw);
} catch (java.util.NoSuchElementException nsee) {
return result;
}
if (tw.trigger.getNextFireTime() == null) {
continue;
}
if (applyMisfire(tw)) {
if (tw.trigger.getNextFireTime() != null) {
timeTriggers.add(tw);
}
continue;
}
if (tw.getTrigger().getNextFireTime().getTime() > noLaterThan + timeWindow) {
timeTriggers.add(tw);
return result;
}
tw.state = TriggerWrapper.STATE_ACQUIRED;
tw.trigger.setFireInstanceId(getFiredTriggerRecordId());
OperableTrigger trig = (OperableTrigger) tw.trigger.clone();
result.add(trig);
if (result.size() == maxCount) {
return result;
}
}
}
}
/**
*
* Inform the JobStore
that the scheduler no longer plans to fire the given Trigger
, that it had previously acquired (reserved).
*
*/
@Override
public void releaseAcquiredTrigger(OperableTrigger trigger) {
synchronized (lock) {
TriggerWrapper tw = triggersByKey.get(trigger.getKey());
if (tw != null && tw.state == TriggerWrapper.STATE_ACQUIRED) {
tw.state = TriggerWrapper.STATE_WAITING;
timeTriggers.add(tw);
}
}
}
/**
*
* Inform the JobStore
that the scheduler is now firing the given Trigger
(executing its associated Job
), that it had previously acquired (reserved).
*
*/
@Override
public List triggersFired(List triggers) {
synchronized (lock) {
List results = new ArrayList();
for (OperableTrigger trigger : triggers) {
TriggerWrapper tw = triggersByKey.get(trigger.getKey());
// was the trigger deleted since being acquired?
if (tw == null || tw.trigger == null) {
continue;
}
// was the trigger completed, paused, blocked, etc. since being acquired?
if (tw.state != TriggerWrapper.STATE_ACQUIRED) {
continue;
}
Calendar cal = null;
if (tw.trigger.getCalendarName() != null) {
cal = retrieveCalendar(tw.trigger.getCalendarName());
if (cal == null) {
continue;
}
}
Date prevFireTime = trigger.getPreviousFireTime();
// in case trigger was replaced between acquiring and firing
timeTriggers.remove(tw);
// call triggered on our copy, and the scheduler's copy
tw.trigger.triggered(cal);
trigger.triggered(cal);
// tw.state = TriggerWrapper.STATE_EXECUTING;
tw.state = TriggerWrapper.STATE_WAITING;
TriggerFiredBundle bndle = new TriggerFiredBundle(retrieveJob(tw.jobKey), trigger, cal, false, new Date(), trigger.getPreviousFireTime(), prevFireTime, trigger.getNextFireTime());
JobDetail job = bndle.getJobDetail();
if (job.isConcurrentExectionDisallowed()) {
ArrayList trigs = getTriggerWrappersForJob(job.getKey());
Iterator itr = trigs.iterator();
while (itr.hasNext()) {
TriggerWrapper ttw = (TriggerWrapper) itr.next();
if (ttw.state == TriggerWrapper.STATE_WAITING) {
ttw.state = TriggerWrapper.STATE_BLOCKED;
}
if (ttw.state == TriggerWrapper.STATE_PAUSED) {
ttw.state = TriggerWrapper.STATE_PAUSED_BLOCKED;
}
timeTriggers.remove(ttw);
}
blockedJobs.add(job.getKey());
} else if (tw.trigger.getNextFireTime() != null) {
synchronized (lock) {
timeTriggers.add(tw);
}
}
results.add(new TriggerFiredResult(bndle));
}
return results;
}
}
/**
*
* Inform the JobStore
that the scheduler has completed the firing of the given Trigger
(and the execution its associated Job
), and that the {@link org.quartz.JobDataMap}
in the given
* JobDetail
should be updated if the Job
is stateful.
*
*/
@Override
public void triggeredJobComplete(OperableTrigger trigger, JobDetail jobDetail, CompletedExecutionInstruction triggerInstCode) {
synchronized (lock) {
JobWrapper jw = jobsByKey.get(jobDetail.getKey());
TriggerWrapper tw = triggersByKey.get(trigger.getKey());
// It's possible that the job is null if:
// 1- it was deleted during execution
// 2- RAMJobStore is being used only for volatile jobs / triggers
// from the JDBC job store
if (jw != null) {
JobDetail jd = jw.jobDetail;
if (jd.isConcurrentExectionDisallowed()) {
blockedJobs.remove(jd.getKey());
ArrayList trigs = getTriggerWrappersForJob(jd.getKey());
for (TriggerWrapper ttw : trigs) {
if (ttw.state == TriggerWrapper.STATE_BLOCKED) {
ttw.state = TriggerWrapper.STATE_WAITING;
timeTriggers.add(ttw);
}
if (ttw.state == TriggerWrapper.STATE_PAUSED_BLOCKED) {
ttw.state = TriggerWrapper.STATE_PAUSED;
}
}
mSignaler.signalSchedulingChange(0L);
}
} else { // even if it was deleted, there may be cleanup to do
blockedJobs.remove(jobDetail.getKey());
}
// check for trigger deleted during execution...
if (tw != null) {
if (triggerInstCode == CompletedExecutionInstruction.DELETE_TRIGGER) {
if (trigger.getNextFireTime() == null) {
// double check for possible reschedule within job
// execution, which would cancel the need to delete...
if (tw.getTrigger().getNextFireTime() == null) {
removeTrigger(trigger.getKey());
}
} else {
removeTrigger(trigger.getKey());
mSignaler.signalSchedulingChange(0L);
}
} else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) {
tw.state = TriggerWrapper.STATE_COMPLETE;
timeTriggers.remove(tw);
mSignaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_ERROR) {
logger.info("Trigger " + trigger.getKey() + " set to ERROR state.");
tw.state = TriggerWrapper.STATE_ERROR;
mSignaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR) {
logger.info("All triggers of Job " + trigger.getJobKey() + " set to ERROR state.");
setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_ERROR);
mSignaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) {
setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_COMPLETE);
mSignaler.signalSchedulingChange(0L);
}
}
}
}
protected void setAllTriggersOfJobToState(JobKey jobKey, int state) {
ArrayList tws = getTriggerWrappersForJob(jobKey);
Iterator itr = tws.iterator();
while (itr.hasNext()) {
TriggerWrapper tw = (TriggerWrapper) itr.next();
tw.state = state;
if (state != TriggerWrapper.STATE_WAITING) {
timeTriggers.remove(tw);
}
}
}
protected String peekTriggers() {
StringBuffer str = new StringBuffer();
TriggerWrapper tw = null;
synchronized (lock) {
for (Iterator valueIter = triggersByKey.values().iterator(); valueIter.hasNext();) {
tw = (TriggerWrapper) valueIter.next();
str.append(tw.trigger.getKey().getName());
str.append("/");
}
}
str.append(" | ");
synchronized (lock) {
Iterator itr = timeTriggers.iterator();
while (itr.hasNext()) {
tw = (TriggerWrapper) itr.next();
str.append(tw.trigger.getKey().getName());
str.append("->");
}
}
return str.toString();
}
/**
* @see org.quartz.spi.JobStore#getPausedTriggerGroups(org.quartz.core.SchedulingContext)
*/
@Override
public Set getPausedTriggerGroups() throws JobPersistenceException {
HashSet set = new HashSet();
set.addAll(pausedTriggerGroups);
return set;
}
@Override
public void setThreadPoolSize(final int poolSize) {
//
}
@Override
public long getEstimatedTimeToReleaseAndAcquireTrigger() {
return 5;
}
}
/**
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Helper Classes. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
class TriggerWrapperComparator implements Comparator {
TriggerTimeComparator ttc = new TriggerTimeComparator();
@Override
public int compare(TriggerWrapper trig1, TriggerWrapper trig2) {
return ttc.compare(trig1.trigger, trig2.trigger);
}
@Override
public boolean equals(Object obj) {
return (obj instanceof TriggerWrapperComparator);
}
}
class JobWrapper {
public JobKey key;
public JobDetail jobDetail;
JobWrapper(JobDetail jobDetail) {
this.jobDetail = jobDetail;
key = jobDetail.getKey();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof JobWrapper) {
JobWrapper jw = (JobWrapper) obj;
if (jw.key.equals(this.key)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return key.hashCode();
}
}
class TriggerWrapper {
public TriggerKey key;
public JobKey jobKey;
public OperableTrigger trigger;
public int state = STATE_WAITING;
public static final int STATE_WAITING = 0;
public static final int STATE_ACQUIRED = 1;
public static final int STATE_EXECUTING = 2;
public static final int STATE_COMPLETE = 3;
public static final int STATE_PAUSED = 4;
public static final int STATE_BLOCKED = 5;
public static final int STATE_PAUSED_BLOCKED = 6;
public static final int STATE_ERROR = 7;
TriggerWrapper(OperableTrigger trigger) {
this.trigger = trigger;
key = trigger.getKey();
this.jobKey = trigger.getJobKey();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TriggerWrapper) {
TriggerWrapper tw = (TriggerWrapper) obj;
if (tw.key.equals(this.key)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return key.hashCode();
}
public OperableTrigger getTrigger() {
return this.trigger;
}
}