io.logspace.agent.shaded.quartz.simpl.RAMJobStore Maven / Gradle / Ivy
/*
* 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 io.logspace.agent.shaded.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.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import io.logspace.agent.shaded.quartz.Calendar;
import io.logspace.agent.shaded.quartz.Job;
import io.logspace.agent.shaded.quartz.JobDataMap;
import io.logspace.agent.shaded.quartz.JobDetail;
import io.logspace.agent.shaded.quartz.JobKey;
import io.logspace.agent.shaded.quartz.JobPersistenceException;
import io.logspace.agent.shaded.quartz.ObjectAlreadyExistsException;
import io.logspace.agent.shaded.quartz.Trigger;
import io.logspace.agent.shaded.quartz.TriggerKey;
import io.logspace.agent.shaded.quartz.Trigger.CompletedExecutionInstruction;
import io.logspace.agent.shaded.quartz.Trigger.TriggerState;
import io.logspace.agent.shaded.quartz.Trigger.TriggerTimeComparator;
import io.logspace.agent.shaded.quartz.impl.JobDetailImpl;
import io.logspace.agent.shaded.quartz.impl.matchers.GroupMatcher;
import io.logspace.agent.shaded.quartz.impl.matchers.StringMatcher;
import io.logspace.agent.shaded.quartz.spi.ClassLoadHelper;
import io.logspace.agent.shaded.quartz.spi.JobStore;
import io.logspace.agent.shaded.quartz.spi.OperableTrigger;
import io.logspace.agent.shaded.quartz.spi.SchedulerSignaler;
import io.logspace.agent.shaded.quartz.spi.TriggerFiredBundle;
import io.logspace.agent.shaded.quartz.spi.TriggerFiredResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.logspace.agent.shaded.quartz.impl.matchers.EverythingMatcher.allTriggers;
/**
*
* This class implements a {@link io.logspace.agent.shaded.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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
protected HashMap jobsByKey = new HashMap(1000);
protected HashMap triggersByKey = new HashMap(1000);
protected HashMap> jobsByGroup = new HashMap>(25);
protected HashMap> triggersByGroup = new HashMap>(25);
protected TreeSet timeTriggers = new TreeSet(new TriggerWrapperComparator());
protected HashMap calendarsByName = new HashMap(25);
protected ArrayList triggers = new ArrayList(1000);
protected final Object lock = new Object();
protected HashSet pausedTriggerGroups = new HashSet();
protected HashSet pausedJobGroups = new HashSet();
protected HashSet blockedJobs = new HashSet();
protected long misfireThreshold = 5000l;
protected SchedulerSignaler signaler;
private final Logger log = LoggerFactory.getLogger(getClass());
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constructors.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
* Create a new RAMJobStore
.
*
*/
public RAMJobStore() {
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
protected Logger getLog() {
return log;
}
/**
*
* Called by the QuartzScheduler before the JobStore
is
* used, in order to give the it a chance to initialize.
*
*/
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler schedSignaler) {
this.signaler = schedSignaler;
getLog().info("RAMJobStore initialized.");
}
public void schedulerStarted() {
// nothing to do
}
public void schedulerPaused() {
// nothing to do
}
public void schedulerResumed() {
// 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 the new misfire threshold
*/
@SuppressWarnings("UnusedDeclaration")
public void setMisfireThreshold(long misfireThreshold) {
if (misfireThreshold < 1) {
throw new IllegalArgumentException("Misfire threshold 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.
*
*/
public void shutdown() {
}
public boolean supportsPersistence() {
return false;
}
/**
* Clear (delete!) all scheduling data - all {@link Job}s, {@link Trigger}s
* {@link Calendar}s.
*
* @throws JobPersistenceException
*/
public void clearAllSchedulingData() throws JobPersistenceException {
synchronized (lock) {
// unschedule jobs (delete triggers)
List lst = getTriggerGroupNames();
for (String group: lst) {
Set keys = getTriggerKeys(GroupMatcher.triggerGroupEquals(group));
for (TriggerKey key: keys) {
removeTrigger(key);
}
}
// delete jobs
lst = getJobGroupNames();
for (String group: lst) {
Set keys = getJobKeys(GroupMatcher.jobGroupEquals(group));
for (JobKey key: keys) {
removeJob(key);
}
}
// delete calendars
lst = getCalendarNames();
for(String name: lst) {
removeCalendar(name);
}
}
}
/**
*
* Store the given {@link io.logspace.agent.shaded.quartz.JobDetail}
and {@link io.logspace.agent.shaded.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.
*/
public void storeJobAndTrigger(JobDetail newJob,
OperableTrigger newTrigger) throws JobPersistenceException {
storeJob(newJob, false);
storeTrigger(newTrigger, false);
}
/**
*
* Store the given {@link io.logspace.agent.shaded.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.
*/
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 io.logspace.agent.shaded.quartz.Job}
with the given
* name, and any {@link io.logspace.agent.shaded.quartz.Trigger}
s that reference
* it.
*
*
* @return true
if a Job
with the given name &
* group was found and removed from the store.
*/
public boolean removeJob(JobKey jobKey) {
boolean found = false;
synchronized (lock) {
List triggersOfJob = getTriggersForJob(jobKey);
for (OperableTrigger trig: triggersOfJob) {
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;
}
public boolean removeJobs(List jobKeys)
throws JobPersistenceException {
boolean allFound = true;
synchronized (lock) {
for(JobKey key: jobKeys)
allFound = removeJob(key) && allFound;
}
return allFound;
}
public boolean removeTriggers(List triggerKeys)
throws JobPersistenceException {
boolean allFound = true;
synchronized (lock) {
for(TriggerKey key: triggerKeys)
allFound = removeTrigger(key) && allFound;
}
return allFound;
}
public void storeJobsAndTriggers(
Map> triggersAndJobs, boolean replace)
throws JobPersistenceException {
synchronized (lock) {
// make sure there are no collisions...
if(!replace) {
for(Entry> e: triggersAndJobs.entrySet()) {
if(checkExists(e.getKey().getKey()))
throw new ObjectAlreadyExistsException(e.getKey());
for(Trigger trigger: e.getValue()) {
if(checkExists(trigger.getKey()))
throw new ObjectAlreadyExistsException(trigger);
}
}
}
// do bulk add...
for(Entry> e: triggersAndJobs.entrySet()) {
storeJob(e.getKey(), true);
for(Trigger trigger: e.getValue()) {
storeTrigger((OperableTrigger) trigger, true);
}
}
}
}
/**
*
* Store the given {@link io.logspace.agent.shaded.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 #pauseTriggers(io.logspace.agent.shaded.quartz.impl.matchers.GroupMatcher)
*/
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 io.logspace.agent.shaded.quartz.Trigger}
with the
* given name.
*
*
* @return true
if a Trigger
with the given
* name & group was found and removed from the store.
*/
public boolean removeTrigger(TriggerKey triggerKey) {
return removeTrigger(triggerKey, true);
}
private boolean removeTrigger(TriggerKey key, boolean removeOrphanedJob) {
boolean found;
synchronized (lock) {
// remove from triggers by FQN map
found = (triggersByKey.remove(key) != null);
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 = 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)) {
signaler.notifySchedulerListenersJobDeleted(jw.key);
}
}
}
}
}
return found;
}
/**
* @see io.logspace.agent.shaded.quartz.spi.JobStore#replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger)
*/
public boolean replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) throws JobPersistenceException {
boolean found;
synchronized (lock) {
// remove from triggers by FQN map
TriggerWrapper tw = triggersByKey.remove(triggerKey);
found = (tw != null);
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 = 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 io.logspace.agent.shaded.quartz.JobDetail}
for the given
* {@link io.logspace.agent.shaded.quartz.Job}
.
*
*
* @return The desired Job
, or null if there is no match.
*/
public JobDetail retrieveJob(JobKey jobKey) {
synchronized(lock) {
JobWrapper jw = jobsByKey.get(jobKey);
return (jw != null) ? (JobDetail)jw.jobDetail.clone() : null;
}
}
/**
*
* Retrieve the given {@link io.logspace.agent.shaded.quartz.Trigger}
.
*
*
* @return The desired Trigger
, or null if there is no
* match.
*/
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 JobPersistenceException
*/
public boolean checkExists(JobKey jobKey) throws JobPersistenceException {
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 JobPersistenceException
*/
public boolean checkExists(TriggerKey triggerKey) throws JobPersistenceException {
synchronized(lock) {
TriggerWrapper tw = triggersByKey.get(triggerKey);
return (tw != null);
}
}
/**
*
* Get the current state of the identified {@link Trigger}
.
*
*
* @see TriggerState#NORMAL
* @see TriggerState#PAUSED
* @see TriggerState#COMPLETE
* @see TriggerState#ERROR
* @see TriggerState#BLOCKED
* @see TriggerState#NONE
*/
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 io.logspace.agent.shaded.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.
*/
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) {
throw new ObjectAlreadyExistsException(
"Calendar with name '" + name + "' already exists.");
} else if (obj != null) {
calendarsByName.remove(name);
}
calendarsByName.put(name, calendar);
if(obj != null && updateTriggers) {
for (TriggerWrapper tw : getTriggerWrappersForCalendar(name)) {
OperableTrigger trig = tw.getTrigger();
boolean removed = timeTriggers.remove(tw);
trig.updateWithNewCalendar(calendar, getMisfireThreshold());
if (removed) {
timeTriggers.add(tw);
}
}
}
}
}
/**
*
* Remove (delete) the {@link io.logspace.agent.shaded.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.
*/
public boolean removeCalendar(String calName)
throws JobPersistenceException {
int numRefs = 0;
synchronized (lock) {
for (TriggerWrapper trigger : triggers) {
OperableTrigger trigg = trigger.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 io.logspace.agent.shaded.quartz.Trigger}
.
*
*
* @param calName
* The name of the Calendar
to be retrieved.
* @return The desired Calendar
, or null if there is no
* match.
*/
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 io.logspace.agent.shaded.quartz.JobDetail}
s that are
* stored in the JobsStore
.
*
*/
public int getNumberOfJobs() {
synchronized (lock) {
return jobsByKey.size();
}
}
/**
*
* Get the number of {@link io.logspace.agent.shaded.quartz.Trigger}
s that are
* stored in the JobsStore
.
*
*/
public int getNumberOfTriggers() {
synchronized (lock) {
return triggers.size();
}
}
/**
*
* Get the number of {@link io.logspace.agent.shaded.quartz.Calendar}
s that are
* stored in the JobsStore
.
*
*/
public int getNumberOfCalendars() {
synchronized (lock) {
return calendarsByName.size();
}
}
/**
*
* Get the names of all of the {@link io.logspace.agent.shaded.quartz.Job}
s that
* match the given groupMatcher.
*
*/
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 io.logspace.agent.shaded.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
).
*
*/
public List getCalendarNames() {
synchronized(lock) {
return new LinkedList(calendarsByName.keySet());
}
}
/**
*
* Get the names of all of the {@link io.logspace.agent.shaded.quartz.Trigger}
s
* that match the given groupMatcher.
*
*/
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 io.logspace.agent.shaded.quartz.Job}
* groups.
*
*/
public List getJobGroupNames() {
List outList;
synchronized (lock) {
outList = new LinkedList(jobsByGroup.keySet());
}
return outList;
}
/**
*
* Get the names of all of the {@link io.logspace.agent.shaded.quartz.Trigger}
* groups.
*
*/
public List getTriggerGroupNames() {
LinkedList outList;
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.
*
*/
public List getTriggersForJob(JobKey jobKey) {
ArrayList trigList = new ArrayList();
synchronized (lock) {
for (TriggerWrapper tw : triggers) {
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 (TriggerWrapper trigger : triggers) {
if (trigger.jobKey.equals(jobKey)) {
trigList.add(trigger);
}
}
}
return trigList;
}
protected ArrayList getTriggerWrappersForCalendar(String calName) {
ArrayList trigList = new ArrayList();
synchronized (lock) {
for (TriggerWrapper tw : triggers) {
String tcalName = tw.getTrigger().getCalendarName();
if (tcalName != null && tcalName.equals(calName)) {
trigList.add(tw);
}
}
}
return trigList;
}
/**
*
* Pause the {@link Trigger}
with the given name.
*
*
*/
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.
*
*
*/
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.triggerGroupEquals(pausedGroup));
for (TriggerKey key: keys) {
pauseTrigger(key);
}
}
}
return pausedGroups;
}
/**
*
* Pause the {@link io.logspace.agent.shaded.quartz.JobDetail}
with the given
* name - by pausing all of its current Trigger
s.
*
*
*/
public void pauseJob(JobKey jobKey) {
synchronized (lock) {
List triggersOfJob = getTriggersForJob(jobKey);
for (OperableTrigger trigger: triggersOfJob) {
pauseTrigger(trigger.getKey());
}
}
}
/**
*
* Pause all of the {@link io.logspace.agent.shaded.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.
*
*/
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.jobGroupEquals(groupName))) {
List triggersOfJob = getTriggersForJob(jobKey);
for (OperableTrigger trigger: triggersOfJob) {
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.
*
*
*/
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.
*
*
*/
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 io.logspace.agent.shaded.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.
*
*
*/
public void resumeJob(JobKey jobKey) {
synchronized (lock) {
List triggersOfJob = getTriggersForJob(jobKey);
for (OperableTrigger trigger: triggersOfJob) {
resumeTrigger(trigger.getKey());
}
}
}
/**
*
* Resume (un-pause) all of the {@link io.logspace.agent.shaded.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.
*
*
*/
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 triggersOfJob = getTriggersForJob(key);
for (OperableTrigger trigger: triggersOfJob) {
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()
* @see #pauseTrigger(io.logspace.agent.shaded.quartz.TriggerKey)
* @see #pauseTriggers(io.logspace.agent.shaded.quartz.impl.matchers.GroupMatcher)
*/
public void pauseAll() {
synchronized (lock) {
List names = getTriggerGroupNames();
for (String name: names) {
pauseTriggers(GroupMatcher.triggerGroupEquals(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()
*/
public void resumeAll() {
synchronized (lock) {
pausedJobGroups.clear();
resumeTriggers(GroupMatcher.anyTriggerGroup());
}
}
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());
}
signaler.notifyTriggerListenersMisfired((OperableTrigger)tw.trigger.clone());
tw.trigger.updateAfterMisfire(cal);
if (tw.trigger.getNextFireTime() == null) {
tw.state = TriggerWrapper.STATE_COMPLETE;
signaler.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(OperableTrigger)
*/
public List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow) {
synchronized (lock) {
List result = new ArrayList();
Set acquiredJobKeysForNoConcurrentExec = new HashSet();
Set excludedTriggers = new HashSet();
long firstAcquiredTriggerFireTime = 0;
// return empty list if store has no triggers.
if (timeTriggers.size() == 0)
return result;
while (true) {
TriggerWrapper tw;
try {
tw = timeTriggers.first();
if (tw == null)
break;
timeTriggers.remove(tw);
} catch (java.util.NoSuchElementException nsee) {
break;
}
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);
break;
}
// If trigger's job is set as @DisallowConcurrentExecution, and it has already been added to result, then
// put it back into the timeTriggers set and continue to search for next trigger.
JobKey jobKey = tw.trigger.getJobKey();
JobDetail job = jobsByKey.get(tw.trigger.getJobKey()).jobDetail;
if (job.isConcurrentExectionDisallowed()) {
if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) {
excludedTriggers.add(tw);
continue; // go to next trigger in store.
} else {
acquiredJobKeysForNoConcurrentExec.add(jobKey);
}
}
tw.state = TriggerWrapper.STATE_ACQUIRED;
tw.trigger.setFireInstanceId(getFiredTriggerRecordId());
OperableTrigger trig = (OperableTrigger) tw.trigger.clone();
result.add(trig);
if(firstAcquiredTriggerFireTime == 0)
firstAcquiredTriggerFireTime = tw.trigger.getNextFireTime().getTime();
if (result.size() == maxCount)
break;
}
// If we did excluded triggers to prevent ACQUIRE state due to DisallowConcurrentExecution, we need to add them back to store.
if (excludedTriggers.size() > 0)
timeTriggers.addAll(excludedTriggers);
return result;
}
}
/**
*
* Inform the JobStore
that the scheduler no longer plans to
* fire the given Trigger
, that it had previously acquired
* (reserved).
*
*/
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).
*
*/
public List triggersFired(List firedTriggers) {
synchronized (lock) {
List results = new ArrayList();
for (OperableTrigger trigger : firedTriggers) {
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());
for (TriggerWrapper ttw : trigs) {
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 io.logspace.agent.shaded.quartz.JobDataMap}
* in the given JobDetail
should be updated if the Job
* is stateful.
*
*/
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.isPersistJobDataAfterExecution()) {
JobDataMap newData = jobDetail.getJobDataMap();
if (newData != null) {
newData = (JobDataMap)newData.clone();
newData.clearDirtyFlag();
}
jd = jd.getJobBuilder().setJobData(newData).build();
jw.jobDetail = jd;
}
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;
}
}
signaler.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());
signaler.signalSchedulingChange(0L);
}
} else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) {
tw.state = TriggerWrapper.STATE_COMPLETE;
timeTriggers.remove(tw);
signaler.signalSchedulingChange(0L);
} else if(triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_ERROR) {
getLog().info("Trigger " + trigger.getKey() + " set to ERROR state.");
tw.state = TriggerWrapper.STATE_ERROR;
signaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_ERROR) {
getLog().info("All triggers of Job "
+ trigger.getJobKey() + " set to ERROR state.");
setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_ERROR);
signaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) {
setAllTriggersOfJobToState(trigger.getJobKey(), TriggerWrapper.STATE_COMPLETE);
signaler.signalSchedulingChange(0L);
}
}
}
}
protected void setAllTriggersOfJobToState(JobKey jobKey, int state) {
ArrayList tws = getTriggerWrappersForJob(jobKey);
for (TriggerWrapper tw : tws) {
tw.state = state;
if (state != TriggerWrapper.STATE_WAITING) {
timeTriggers.remove(tw);
}
}
}
@SuppressWarnings("UnusedDeclaration")
protected String peekTriggers() {
StringBuilder str = new StringBuilder();
synchronized (lock) {
for (TriggerWrapper triggerWrapper : triggersByKey.values()) {
str.append(triggerWrapper.trigger.getKey().getName());
str.append("/");
}
}
str.append(" | ");
synchronized (lock) {
for (TriggerWrapper timeTrigger : timeTriggers) {
str.append(timeTrigger.trigger.getKey().getName());
str.append("->");
}
}
return str.toString();
}
/**
* @see io.logspace.agent.shaded.quartz.spi.JobStore#getPausedTriggerGroups()
*/
public Set getPausedTriggerGroups() throws JobPersistenceException {
HashSet set = new HashSet();
set.addAll(pausedTriggerGroups);
return set;
}
public void setInstanceId(String schedInstId) {
//
}
public void setInstanceName(String schedName) {
//
}
public void setThreadPoolSize(final int poolSize) {
//
}
public long getEstimatedTimeToReleaseAndAcquireTrigger() {
return 5;
}
public boolean isClustered() {
return false;
}
}
/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Helper Classes. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
class TriggerWrapperComparator implements Comparator, java.io.Serializable {
private static final long serialVersionUID = 8809557142191514261L;
TriggerTimeComparator ttc = new TriggerTimeComparator();
public int compare(TriggerWrapper trig1, TriggerWrapper trig2) {
return ttc.compare(trig1.trigger, trig2.trigger);
}
@Override
public boolean equals(Object obj) {
return (obj instanceof TriggerWrapperComparator);
}
@Override
public int hashCode() {
return super.hashCode();
}
}
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 final TriggerKey key;
public final JobKey jobKey;
public final OperableTrigger trigger;
public int state = STATE_WAITING;
public static final int STATE_WAITING = 0;
public static final int STATE_ACQUIRED = 1;
@SuppressWarnings("UnusedDeclaration")
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) {
if(trigger == null)
throw new IllegalArgumentException("Trigger cannot be null!");
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;
}
}