com.bikeemotion.quartz.jobstore.hazelcast.HazelcastJobStore Maven / Gradle / Ivy
package com.bikeemotion.quartz.jobstore.hazelcast;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.ISet;
import com.hazelcast.core.MultiMap;
import com.hazelcast.query.Predicate;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.quartz.Calendar;
import org.quartz.DateBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.JobPersistenceException;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.SchedulerConfigException;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.matchers.StringMatcher;
import org.quartz.spi.ClassLoadHelper;
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;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.ACQUIRED;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.BLOCKED;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.NORMAL;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.PAUSED;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.STATE_COMPLETED;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.WAITING;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerState.toClassicTriggerState;
import static com.bikeemotion.quartz.jobstore.hazelcast.TriggerWrapper.newTriggerWrapper;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static org.quartz.impl.matchers.StringMatcher.StringOperatorName.EQUALS;
/**
*
* @author Flavio Ferreira
*
* Thanks Antoine Méausoone for starting the work.
*/
public class HazelcastJobStore implements JobStore, Serializable {
private static final Logger LOG = LoggerFactory.getLogger(HazelcastJobStore.class);
private static HazelcastInstance hazelcastClient;
public static void setHazelcastClient(HazelcastInstance aHazelcastClient) {
hazelcastClient = aHazelcastClient;
}
private final String HC_JOB_STORE_MAP_JOB = "job-store-map-job";
private final String HC_JOB_STORE_MAP_JOB_BY_GROUP_MAP = "job-store-map-job-by-group-map";
private final String HC_JOB_STORE_TRIGGER_BY_KEY_MAP = "job-store-trigger-by-key-map";
private final String HC_JOB_STORE_TRIGGER_KEY_BY_GROUP_MAP = "job-trigger-key-by-group-map";
private final String HC_JOB_STORE_PAUSED_TRIGGER_GROUPS = "job-paused-trigger-groups";
private final String HC_JOB_STORE_PAUSED_JOB_GROUPS = "job-paused-job-groups";
private final String HC_JOB_CALENDAR_MAP = "job-calendar-map";
private static long ftrCtr = System.currentTimeMillis();
private SchedulerSignaler schedSignaler;
private IMap jobsByKey;
private IMap triggersByKey;
private MultiMap jobsByGroup;
private MultiMap triggersByGroup;
private IMap calendarsByName;
private ISet pausedTriggerGroups;
private ISet pausedJobGroups;
private volatile boolean schedulerRunning = false;
private long misfireThreshold = 5000;
private long triggerReleaseThreshold = 60000;
private String instanceId;
private String instanceName;
private boolean shutdownHazelcastOnShutdown = true;
public static final DateTimeFormatter FORMATTER = ISODateTimeFormat.basicDateTimeNoMillis();
@Override
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler)
throws SchedulerConfigException {
LOG.debug("Initializing Hazelcast Job Store..");
this.schedSignaler = signaler;
if (hazelcastClient == null) {
LOG.warn("Starting new local hazelcast client since not hazelcast instance setted before starting scheduler.");
hazelcastClient = Hazelcast.newHazelcastInstance();
}
// initializing hazelcast maps
LOG.debug("Initializing hazelcast maps...");
jobsByKey = hazelcastClient.getMap(HC_JOB_STORE_MAP_JOB);
triggersByKey = hazelcastClient.getMap(HC_JOB_STORE_TRIGGER_BY_KEY_MAP);
jobsByGroup = hazelcastClient.getMultiMap(HC_JOB_STORE_MAP_JOB_BY_GROUP_MAP);
triggersByGroup = hazelcastClient.getMultiMap(HC_JOB_STORE_TRIGGER_KEY_BY_GROUP_MAP);
pausedTriggerGroups = hazelcastClient.getSet(HC_JOB_STORE_PAUSED_TRIGGER_GROUPS);
pausedJobGroups = hazelcastClient.getSet(HC_JOB_STORE_PAUSED_JOB_GROUPS);
calendarsByName = hazelcastClient.getMap(HC_JOB_CALENDAR_MAP);
triggersByKey.addIndex("nextFireTime", true);
LOG.debug("Hazelcast Job Store Initialized.");
}
@Override
public void schedulerStarted()
throws SchedulerException {
LOG.info("Hazelcast Job Store started successfully");
schedulerRunning = true;
}
@Override
public void schedulerPaused() {
schedulerRunning = false;
}
@Override
public void schedulerResumed() {
schedulerRunning = true;
}
@Override
public void shutdown() {
if (shutdownHazelcastOnShutdown) {
hazelcastClient.shutdown();
}
}
@Override
public boolean supportsPersistence() {
return true;
}
@Override
public long getEstimatedTimeToReleaseAndAcquireTrigger() {
return 25;
}
@Override
public boolean isClustered() {
return true;
}
@Override
public void storeJobAndTrigger(final JobDetail newJob,
final OperableTrigger newTrigger)
throws ObjectAlreadyExistsException,
JobPersistenceException {
storeJob(newJob, false);
storeTrigger(newTrigger, false);
}
@Override
public void storeJob(final JobDetail job, boolean replaceExisting)
throws ObjectAlreadyExistsException, JobPersistenceException {
final JobDetail newJob = (JobDetail) job.clone();
final JobKey newJobKey = newJob.getKey();
if (jobsByKey.containsKey(newJobKey) && !replaceExisting) {
throw new ObjectAlreadyExistsException(newJob);
}
jobsByKey.lock(newJobKey, 5, TimeUnit.SECONDS);
try {
jobsByKey.set(newJobKey, newJob);
jobsByGroup.put(newJobKey.getGroup(), newJobKey);
} finally {
try {
jobsByKey.unlock(newJobKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public void storeJobsAndTriggers(
final Map> triggersAndJobs,
boolean replace)
throws ObjectAlreadyExistsException,
JobPersistenceException {
if (!replace) {
// validate if anything already exists
for (final Entry> e : triggersAndJobs
.entrySet()) {
final JobDetail jobDetail = e.getKey();
if (checkExists(jobDetail.getKey())) {
throw new ObjectAlreadyExistsException(jobDetail);
}
for (final Trigger trigger : e.getValue()) {
if (checkExists(trigger.getKey())) {
throw new ObjectAlreadyExistsException(trigger);
}
}
}
}
// do bulk add...
for (final Entry> e : triggersAndJobs
.entrySet()) {
storeJob(e.getKey(), true);
for (final Trigger trigger : e.getValue()) {
storeTrigger((OperableTrigger) trigger, true);
}
}
}
@Override
public boolean removeJob(final JobKey jobKey)
throws JobPersistenceException {
boolean removed = false;
if (jobsByKey.containsKey(jobKey)) {
final List triggersForJob = getTriggersForJob(jobKey);
for (final OperableTrigger trigger : triggersForJob) {
if(!removeTrigger(trigger.getKey(), false)){
LOG.warn("Error deleting trigger [{}] of job [{}] .", trigger, jobKey);
return false;
}
}
jobsByKey.lock(jobKey, 5, TimeUnit.MILLISECONDS);
try {
jobsByGroup.remove(jobKey.getGroup(), jobKey);
removed = jobsByKey.remove(jobKey) != null;
} finally {
try {
jobsByKey.unlock(jobKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
return removed;
}
@Override
public boolean removeJobs(final List jobKeys)
throws JobPersistenceException {
boolean allRemoved = true;
for (final JobKey key : jobKeys) {
allRemoved = removeJob(key) && allRemoved;
}
return allRemoved;
}
@Override
public JobDetail retrieveJob(final JobKey jobKey)
throws JobPersistenceException {
return jobKey != null && jobsByKey.containsKey(jobKey)
? (JobDetail) jobsByKey
.get(jobKey).clone()
: null;
}
@Override
public void storeTrigger(OperableTrigger trigger, boolean replaceExisting)
throws ObjectAlreadyExistsException, JobPersistenceException {
final OperableTrigger newTrigger = (OperableTrigger) trigger.clone();
final TriggerKey triggerKey = newTrigger.getKey();
triggersByKey.lock(triggerKey, 5, TimeUnit.SECONDS);
try {
boolean containsKey = triggersByKey.containsKey(triggerKey);
if (containsKey && !replaceExisting) {
throw new ObjectAlreadyExistsException(newTrigger);
}
if (retrieveJob(newTrigger.getJobKey()) == null) {
throw new JobPersistenceException("The job (" + newTrigger.getJobKey()
+ ") referenced by the trigger does not exist.");
}
boolean shouldBePaused = pausedJobGroups.contains(newTrigger.getJobKey()
.getGroup()) || pausedTriggerGroups.contains(triggerKey.getGroup());
final TriggerState state = shouldBePaused
? PAUSED
: NORMAL;
final TriggerWrapper newTriggerWrapper = newTriggerWrapper(newTrigger, state);
triggersByKey.set(newTriggerWrapper.key, newTriggerWrapper);
triggersByGroup.put(triggerKey.getGroup(), triggerKey);
} finally {
try {
triggersByKey.unlock(triggerKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public boolean removeTrigger(TriggerKey triggerKey)
throws JobPersistenceException {
return removeTrigger(triggerKey, true);
}
@Override
public boolean removeTriggers(final List triggerKeys)
throws JobPersistenceException {
boolean allRemoved = true;
for (TriggerKey key : triggerKeys) {
allRemoved = removeTrigger(key) && allRemoved;
}
return allRemoved;
}
@Override
public boolean replaceTrigger(final TriggerKey triggerKey,
final OperableTrigger newTrigger)
throws JobPersistenceException {
newTrigger.setKey(triggerKey);
storeTrigger(newTrigger, true);
return true;
}
@Override
public OperableTrigger retrieveTrigger(final TriggerKey triggerKey)
throws JobPersistenceException {
return triggerKey != null && triggersByKey.containsKey(triggerKey)
? (OperableTrigger) triggersByKey
.get(triggerKey).getTrigger().clone()
: null;
}
@Override
public boolean checkExists(JobKey jobKey)
throws JobPersistenceException {
return jobsByKey.containsKey(jobKey);
}
@Override
public boolean checkExists(TriggerKey triggerKey)
throws JobPersistenceException {
return triggersByKey.containsKey(triggerKey);
}
@Override
public void clearAllSchedulingData()
throws JobPersistenceException {
jobsByKey.clear();
triggersByKey.clear();
jobsByGroup.clear();
triggersByGroup.clear();
calendarsByName.clear();
pausedTriggerGroups.clear();
pausedJobGroups.clear();
}
@Override
public void storeCalendar(final String calName, final Calendar cal,
boolean replaceExisting, boolean updateTriggers)
throws ObjectAlreadyExistsException, JobPersistenceException {
final Calendar calendar = (Calendar) cal.clone();
if (calendarsByName.containsKey(calName) && !replaceExisting) {
throw new ObjectAlreadyExistsException("Calendar with name '" + calName
+ "' already exists.");
} else {
calendarsByName.set(calName, calendar);
}
}
@Override
public boolean removeCalendar(String calName)
throws JobPersistenceException {
int numRefs = 0;
for (TriggerWrapper trigger : triggersByKey.values()) {
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);
}
@Override
public Calendar retrieveCalendar(String calName)
throws JobPersistenceException {
return calendarsByName.get(calName);
}
@Override
public int getNumberOfJobs()
throws JobPersistenceException {
return jobsByKey.size();
}
@Override
public int getNumberOfTriggers()
throws JobPersistenceException {
return triggersByKey.size();
}
@Override
public int getNumberOfCalendars()
throws JobPersistenceException {
return calendarsByName.size();
}
@Override
public Set getJobKeys(final GroupMatcher matcher)
throws JobPersistenceException {
Set outList = null;
final StringMatcher.StringOperatorName operator = matcher
.getCompareWithOperator();
final String groupNameCompareValue = matcher.getCompareToValue();
switch (operator) {
case EQUALS:
final Collection jobKeys = jobsByGroup.get(groupNameCompareValue);
if (jobKeys != null) {
outList = new HashSet<>();
for (JobKey jobKey : jobKeys) {
if (jobKey != null) {
outList.add(jobKey);
}
}
}
break;
default:
for (String groupName : jobsByGroup.keySet()) {
if (operator.evaluate(groupName, groupNameCompareValue)) {
if (outList == null) {
outList = new HashSet<>();
}
for (JobKey jobKey : jobsByGroup.get(groupName)) {
if (jobKey != null) {
outList.add(jobKey);
}
}
}
}
}
return outList == null
? java.util.Collections. emptySet()
: outList;
}
@Override
public Set getTriggerKeys(GroupMatcher matcher)
throws JobPersistenceException {
Set outList = null;
StringMatcher.StringOperatorName operator = matcher
.getCompareWithOperator();
String groupNameCompareValue = matcher.getCompareToValue();
switch (operator) {
case EQUALS:
Collection triggerKeys = triggersByGroup
.get(groupNameCompareValue);
if (triggerKeys != null) {
outList = newHashSet();
for (TriggerKey triggerKey : triggerKeys) {
if (triggerKey != null) {
outList.add(triggerKey);
}
}
}
break;
default:
for (String groupName : triggersByGroup.keySet()) {
if (operator.evaluate(groupName, groupNameCompareValue)) {
if (outList == null) {
outList = newHashSet();
}
for (TriggerKey triggerKey : triggersByGroup.get(groupName)) {
if (triggerKey != null) {
outList.add(triggerKey);
}
}
}
}
}
return outList == null
? java.util.Collections. emptySet()
: outList;
}
@Override
public List getJobGroupNames()
throws JobPersistenceException {
return newArrayList(jobsByGroup.keySet());
}
@Override
public List getTriggerGroupNames()
throws JobPersistenceException {
return new LinkedList<>(triggersByGroup.keySet());
}
@Override
public List getCalendarNames()
throws JobPersistenceException {
return new LinkedList<>(calendarsByName.keySet());
}
@Override
public List getTriggersForJob(final JobKey jobKey)
throws JobPersistenceException {
if (jobKey == null) {
return Collections.emptyList();
}
return triggersByKey.values(new TriggerByJobPredicate(jobKey))
.stream()
.map(v -> (OperableTrigger) v.getTrigger())
.collect(Collectors.toList());
}
@Override
public void pauseTrigger(TriggerKey triggerKey)
throws JobPersistenceException {
triggersByKey.lock(triggerKey, 5, TimeUnit.SECONDS);
try {
final TriggerWrapper newTrigger = newTriggerWrapper(triggersByKey.get(triggerKey), PAUSED);
triggersByKey.set(triggerKey, newTrigger);
} finally {
try {
triggersByKey.unlock(triggerKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public org.quartz.Trigger.TriggerState getTriggerState(TriggerKey triggerKey)
throws JobPersistenceException {
triggersByKey.lock(triggerKey, 5, TimeUnit.SECONDS);
org.quartz.Trigger.TriggerState result = org.quartz.Trigger.TriggerState.NONE;
try {
TriggerWrapper tw = triggersByKey.get(triggerKey);
if (tw != null) {
result = toClassicTriggerState(tw.getState());
}
} finally {
try {
triggersByKey.unlock(triggerKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
return result;
}
@Override
public void resumeTrigger(TriggerKey triggerKey)
throws JobPersistenceException {
triggersByKey.lock(triggerKey, 5, TimeUnit.SECONDS);
try {
if (schedulerRunning) {
final TriggerWrapper newTrigger = newTriggerWrapper(triggersByKey.get(triggerKey), NORMAL);
triggersByKey.set(newTrigger.key, newTrigger);
}
} finally {
try {
triggersByKey.unlock(triggerKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public Collection pauseTriggers(GroupMatcher matcher)
throws JobPersistenceException {
List 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;
}
@Override
public Collection resumeTriggers(GroupMatcher matcher)
throws JobPersistenceException {
Set resumeGroups = new HashSet<>();
Set keys = getTriggerKeys(matcher);
for (TriggerKey triggerKey : keys) {
resumeGroups.add(triggerKey.getGroup());
TriggerWrapper tw = triggersByKey.get(triggerKey);
OperableTrigger trigger = tw.getTrigger();
String jobGroup = trigger.getJobKey().getGroup();
if (pausedJobGroups.contains(jobGroup)) {
continue;
}
resumeTrigger(triggerKey);
}
for (String group : resumeGroups) {
pausedTriggerGroups.remove(group);
}
return new ArrayList<>(resumeGroups);
}
@Override
public void pauseJob(JobKey jobKey)
throws JobPersistenceException {
boolean found = jobsByKey.containsKey(jobKey);
if (!found) {
return;
}
jobsByKey.lock(jobKey, 5, TimeUnit.SECONDS);
try {
List triggersForJob = getTriggersForJob(jobKey);
for (OperableTrigger trigger : triggersForJob) {
pauseTrigger(trigger.getKey());
}
} finally {
try {
jobsByKey.unlock(jobKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public void resumeJob(JobKey jobKey)
throws JobPersistenceException {
boolean found = jobsByKey.containsKey(jobKey);
if (!found) {
return;
}
jobsByKey.lock(jobKey, 5, TimeUnit.SECONDS);
try {
List triggersForJob = getTriggersForJob(jobKey);
for (OperableTrigger trigger : triggersForJob) {
resumeTrigger(trigger.getKey());
}
} finally {
try {
jobsByKey.unlock(jobKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public Collection pauseJobs(GroupMatcher groupMatcher)
throws JobPersistenceException {
List pausedGroups = new LinkedList<>();
StringMatcher.StringOperatorName operator = groupMatcher
.getCompareWithOperator();
switch (operator) {
case EQUALS:
if (pausedJobGroups.add(groupMatcher.getCompareToValue())) {
pausedGroups.add(groupMatcher.getCompareToValue());
}
break;
default:
for (String jobGroup : jobsByGroup.keySet()) {
if (operator.evaluate(jobGroup, groupMatcher.getCompareToValue())) {
if (pausedJobGroups.add(jobGroup)) {
pausedGroups.add(jobGroup);
}
}
}
}
for (String groupName : pausedGroups) {
for (JobKey jobKey : getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
pauseJob(jobKey);
}
}
return pausedGroups;
}
@Override
public Collection resumeJobs(GroupMatcher matcher)
throws JobPersistenceException {
Set resumeGroups = new HashSet<>();
Set jobKeys = getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
resumeGroups.add(jobKey.getGroup());
resumeJob(jobKey);
}
resumeGroups.stream().forEach((group) -> {
pausedJobGroups.remove(group);
});
return new ArrayList<>(resumeGroups);
}
@Override
public Set getPausedTriggerGroups()
throws JobPersistenceException {
return new HashSet<>(pausedTriggerGroups);
}
@Override
public void pauseAll()
throws JobPersistenceException {
for (String triggerGroup : triggersByGroup.keySet()) {
pauseTriggers(GroupMatcher.triggerGroupEquals(triggerGroup));
}
}
@Override
public void resumeAll()
throws JobPersistenceException {
List triggerGroupNames = getTriggerGroupNames();
for (String triggerGroup : triggerGroupNames) {
resumeTriggers(GroupMatcher.triggerGroupEquals(triggerGroup));
}
}
/**
* @return @throws org.quartz.JobPersistenceException
* @see org.quartz.spi.JobStore#acquireNextTriggers(long, int, long)
*
* @param noLaterThan
* highest value of getNextFireTime()
of the
* triggers (exclusive)
* @param timeWindow
* highest value of getNextFireTime()
of the
* triggers (inclusive)
* @param maxCount
* maximum number of trigger keys allow to acquired in the
* returning list.
*/
@Override
public List acquireNextTriggers(long noLaterThan,
int maxCount, long timeWindow)
throws JobPersistenceException {
if (triggersByKey.isEmpty()) {
return Collections.EMPTY_LIST;
}
long limit = noLaterThan + timeWindow;
List result = new ArrayList<>();
Set acquiredJobKeysForNoConcurrentExec = new HashSet<>();
Set excludedTriggers = new HashSet<>();
// ordering triggers to try to ensure firetime order
List orderedTriggers = new ArrayList<>(triggersByKey.values(new TriggersPredicate(limit)));
Collections.sort(orderedTriggers, (o1, o2) -> o1.getNextFireTime().compareTo(o2.getNextFireTime()));
for (int i = 0; i < orderedTriggers.size(); i++) {
TriggerWrapper tw = orderedTriggers.get(i);
// proced after the other jobstore already not blocked
triggersByKey.lock(tw.key, 5, TimeUnit.SECONDS);
try {
// when the trigger was in acquired state for to much time
if (tw.getState() == ACQUIRED && (tw.getAcquiredAt() == null
|| tw.getAcquiredAt() + triggerReleaseThreshold + timeWindow < limit)) {
LOG.warn("Found a lost trigger [{}] that must be released at [{}]", tw, limit);
releaseAcquiredTrigger(tw.trigger);
tw = triggersByKey.get(tw.key);
}
if (tw.getState() != NORMAL && tw.getState() != WAITING) {
continue;
}
if (tw.trigger.getNextFireTime() == null) {
continue;
}
if (applyMisfire(tw)) {
LOG.debug("Misfire applied {}", tw);
if (tw.trigger.getNextFireTime() != null) {
tw = newTriggerWrapper(tw, NORMAL);
} else {
continue;
}
}
if (tw.getTrigger().getNextFireTime().getTime() > limit) {
storeTriggerWrapper(newTriggerWrapper(tw, NORMAL));
continue;
}
// 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.
final JobKey jobKey = tw.trigger.getJobKey();
final JobDetail job = jobsByKey.get(tw.trigger.getJobKey());
if (job.isConcurrentExectionDisallowed()) {
if (acquiredJobKeysForNoConcurrentExec.contains(jobKey)) {
// triggers.remove(tw); // TODO
excludedTriggers.add(tw);
continue; // go to next trigger in queue.
} else {
acquiredJobKeysForNoConcurrentExec.add(jobKey);
}
}
OperableTrigger trig = (OperableTrigger) tw.trigger.clone();
trig.setFireInstanceId(getFiredTriggerRecordId());
storeTriggerWrapper(newTriggerWrapper(trig, ACQUIRED));
result.add(trig);
if (result.size() == maxCount) {
break;
}
} finally {
try {
triggersByKey.unlock(tw.key);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
// If we did excluded triggers to prevent ACQUIRE state due to
// DisallowConcurrentExecution, we need to add
// them back to store.
if (excludedTriggers.size() > 0) {
//TODO
// triggers.addAll(excludedTriggers);
}
return result;
}
@Override
public void releaseAcquiredTrigger(OperableTrigger trigger) {
TriggerKey triggerKey = trigger.getKey();
triggersByKey.lock(triggerKey, 5, TimeUnit.SECONDS);
try {
storeTriggerWrapper(newTriggerWrapper(trigger, WAITING));
} finally {
try {
triggersByKey.unlock(triggerKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
@Override
public List triggersFired(
List firedTriggers)
throws JobPersistenceException {
List results = new ArrayList<>();
for (OperableTrigger trigger : firedTriggers) {
triggersByKey.lock(trigger.getKey(), 5, TimeUnit.SECONDS);
try {
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.getState() != ACQUIRED) {
continue;
}
Calendar cal = null;
if (tw.trigger.getCalendarName() != null) {
cal = retrieveCalendar(tw.trigger.getCalendarName());
if (cal == null) {
continue;
}
}
Date prevFireTime = trigger.getPreviousFireTime();
// call triggered on our copy, and the scheduler's copy
tw.trigger.triggered(cal);
trigger.triggered(cal);
tw = newTriggerWrapper(trigger, 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.getState() == WAITING) {
ttw = newTriggerWrapper(ttw, BLOCKED);
}
if (ttw.getState() == PAUSED) {
ttw = newTriggerWrapper(ttw, BLOCKED);
}
storeTriggerWrapper(ttw);
}
} else if (tw.trigger.getNextFireTime() != null) {
storeTriggerWrapper(tw);
}
results.add(new TriggerFiredResult(bndle));
} finally {
try {
triggersByKey.unlock(trigger.getKey());
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
return results;
}
@Override
public void triggeredJobComplete(
OperableTrigger trigger,
JobDetail jobDetail,
Trigger.CompletedExecutionInstruction triggerInstCode) {
if (jobDetail.isPersistJobDataAfterExecution()) {
JobKey jobKey = jobDetail.getKey();
jobsByKey.lock(jobKey, 5, TimeUnit.SECONDS);
try {
jobsByKey.set(jobKey, jobDetail);
jobsByGroup.put(jobKey.getGroup(), jobKey);
} finally {
try {
jobsByKey.unlock(jobKey);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
}
}
@Override
public void setInstanceName(final String instanceName) {
this.instanceName = instanceName;
}
@Override
public void setInstanceId(String instanceId) {
this.instanceId = instanceId;
}
public void setShutdownHazelcastOnShutdown(boolean shutdownHazelcastOnShutdown) {
this.shutdownHazelcastOnShutdown = shutdownHazelcastOnShutdown;
}
public long getMisfireThreshold() {
return misfireThreshold;
}
public void setMisfireThreshold(long misfireThreshold) {
this.misfireThreshold = misfireThreshold;
}
@Override
public void setThreadPoolSize(int poolSize) {
// not need
}
private ArrayList getTriggerWrappersForJob(JobKey jobKey) {
ArrayList trigList = new ArrayList<>();
triggersByKey.values().stream().filter((trigger) -> (trigger.jobKey.equals(jobKey))).forEach((trigger) -> {
trigList.add(trigger);
});
return trigList;
}
private boolean applyMisfire(TriggerWrapper tw)
throws JobPersistenceException {
long misfireTime = DateBuilder.newDate().build().getTime();
if (misfireThreshold > 0) {
misfireTime -= misfireThreshold;
}
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());
}
this.schedSignaler
.notifyTriggerListenersMisfired((OperableTrigger) tw.trigger.clone());
tw.trigger.updateAfterMisfire(cal);
if (tw.trigger.getNextFireTime() == null) {
storeTriggerWrapper(newTriggerWrapper(tw, STATE_COMPLETED));
schedSignaler.notifySchedulerListenersFinalized(tw.trigger);
} else if (tnft.equals(tw.trigger.getNextFireTime())) {
return false;
}
return true;
}
private synchronized String getFiredTriggerRecordId() {
return instanceId + ftrCtr++;
}
private boolean removeTrigger(TriggerKey key, boolean removeOrphanedJob)
throws JobPersistenceException {
boolean removed = false;
// remove from triggers by FQN map
triggersByKey.lock(key, 5, TimeUnit.SECONDS);
try {
final TriggerWrapper tw = triggersByKey.remove(key);
removed = tw != null;
if (removed) {
// remove from triggers by group
triggersByGroup.remove(key.getGroup(), key);
// triggers.remove(tw);
if (removeOrphanedJob) {
JobDetail job = jobsByKey.get(tw.jobKey);
List trigs = getTriggersForJob(tw.jobKey);
if ((trigs == null || trigs.isEmpty()) && !job.isDurable()) {
if (removeJob(job.getKey())) {
schedSignaler.notifySchedulerListenersJobDeleted(job.getKey());
}
}
}
}
} finally {
try {
triggersByKey.unlock(key);
} catch (IllegalMonitorStateException ex) {
LOG.warn("Error unlocking since it is already released.", ex);
}
}
return removed;
}
private void storeTriggerWrapper(final TriggerWrapper tw) {
triggersByKey.set(tw.key, tw);
}
/**
* Set the max time which a acquired trigger must be released.
* It should be > 30000, since quartz executes acquireNextTriggers in a 30000 interval
*
* @param triggerReleaseThreshold
*/
public void setTriggerReleaseThreshold(long triggerReleaseThreshold) {
if(triggerReleaseThreshold > 30000){
LOG.warn("Try to increase your trigger release time threashold since quartz acquireNextTriggers in a 30000 interval");
}
this.triggerReleaseThreshold = triggerReleaseThreshold;
}
}
/**
* Filter triggers with a given job key
*/
class TriggerByJobPredicate implements Predicate {
private JobKey key;
public TriggerByJobPredicate(JobKey key) {
this.key = key;
}
@Override
public boolean apply(Entry entry) {
return key != null && entry != null && key.equals(entry.getValue().jobKey);
}
}
/**
* Filter triggers with a given fire start time, end time and state.
*/
class TriggersPredicate implements Predicate {
long noLaterThanWithTimeWindow;
public TriggersPredicate(long noLaterThanWithTimeWindow) {
this.noLaterThanWithTimeWindow = noLaterThanWithTimeWindow;
}
@Override
public boolean apply(Entry entry) {
if (entry.getValue() == null || entry.getValue().getNextFireTime() == null) {
return false;
}
return entry.getValue().getNextFireTime() <= noLaterThanWithTimeWindow;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy