Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.openejb.terracotta.quartz.DefaultClusteredJobStore Maven / Gradle / Ivy
/*
* All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. 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.terracotta.quartz;
import org.quartz.Calendar;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.JobPersistenceException;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.matchers.StringMatcher;
import org.quartz.impl.matchers.StringMatcher.StringOperatorName;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.quartz.spi.ClassLoadHelper;
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 org.terracotta.quartz.collections.TimeTriggerSet;
import org.terracotta.quartz.collections.ToolkitDSHolder;
import org.terracotta.quartz.wrappers.DefaultWrapperFactory;
import org.terracotta.quartz.wrappers.FiredTrigger;
import org.terracotta.quartz.wrappers.JobFacade;
import org.terracotta.quartz.wrappers.JobWrapper;
import org.terracotta.quartz.wrappers.TriggerFacade;
import org.terracotta.quartz.wrappers.TriggerWrapper;
import org.terracotta.quartz.wrappers.TriggerWrapper.TriggerState;
import org.terracotta.quartz.wrappers.WrapperFactory;
import org.terracotta.toolkit.Toolkit;
import org.terracotta.toolkit.atomic.ToolkitTransactionType;
import org.terracotta.toolkit.store.ToolkitStore;
import org.terracotta.toolkit.cluster.ClusterEvent;
import org.terracotta.toolkit.cluster.ClusterInfo;
import org.terracotta.toolkit.cluster.ClusterNode;
import org.terracotta.toolkit.concurrent.locks.ToolkitLock;
import org.terracotta.toolkit.internal.ToolkitInternal;
import org.terracotta.toolkit.internal.concurrent.locks.ToolkitLockTypeInternal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.terracotta.toolkit.rejoin.RejoinException;
/**
*
* This class implements a {@link org.quartz.spi.JobStore}
that utilizes Terracotta as its storage device.
*
*
* This code is largely a cut-n-paste of RamJobStore and the world would be a better place if the duplicate code could
* be refactored
*
*/
class DefaultClusteredJobStore implements ClusteredJobStore {
private final ToolkitDSHolder toolkitDSHolder;
private final Toolkit toolkit;
private final JobFacade jobFacade;
private final TriggerFacade triggerFacade;
private final TimeTriggerSet timeTriggers;
private final ToolkitStore calendarsByName;
private long misfireThreshold = 60000L;
private final ToolkitLockTypeInternal lockType;
private transient final ToolkitLock lock;
private final ClusterInfo clusterInfo;
private final WrapperFactory wrapperFactory;
private long ftrCtr;
private volatile SchedulerSignaler signaler;
private final Logger logger;
private volatile String terracottaClientId;
private long estimatedTimeToReleaseAndAcquireTrigger = 15L;
private volatile LocalLockState localStateLock;
private volatile TriggerRemovedFromCandidateFiringListHandler triggerRemovedFromCandidateFiringListHandler;
private volatile boolean toolkitShutdown;
private long retryInterval;
// This is a hack to prevent certain objects from ever being flushed. "this" should never be flushed (at least not
// until the scheduler is shutdown) since it is referenced from the scheduler (which is not a shared object)
// private transient Set hardRefs = new HashSet();
public DefaultClusteredJobStore(boolean synchWrite, Toolkit toolkit, String jobStoreName) {
this(synchWrite, toolkit, jobStoreName, new ToolkitDSHolder(jobStoreName, toolkit), new DefaultWrapperFactory());
}
public DefaultClusteredJobStore(boolean synchWrite, Toolkit toolkit, String jobStoreName,
ToolkitDSHolder toolkitDSHolder, WrapperFactory wrapperFactory) {
this.toolkit = toolkit;
this.wrapperFactory = wrapperFactory;
this.clusterInfo = toolkit.getClusterInfo();
this.toolkitDSHolder = toolkitDSHolder;
this.jobFacade = new JobFacade(toolkitDSHolder);
this.triggerFacade = new TriggerFacade(toolkitDSHolder);
this.timeTriggers = toolkitDSHolder.getOrCreateTimeTriggerSet();
this.calendarsByName = toolkitDSHolder.getOrCreateCalendarWrapperMap();
this.lockType = synchWrite ? ToolkitLockTypeInternal.SYNCHRONOUS_WRITE : ToolkitLockTypeInternal.WRITE;
ToolkitTransactionType txnType = synchWrite ? ToolkitTransactionType.SYNC : ToolkitTransactionType.NORMAL;
this.lock = new TransactionControllingLock((ToolkitInternal) toolkit, toolkitDSHolder.getLock(lockType), txnType);
this.logger = LoggerFactory.getLogger(getClass());
getLog().info("Synchronous write locking is [" + synchWrite + "]");
}
// private synchronized boolean hardRef(Object obj) {
// if (hardRefs == null) {
// hardRefs = new HashSet();
// }
//
// return hardRefs.add(obj);
// }
private Logger getLog() {
return logger;
}
private void disable() {
toolkitShutdown = true;
try {
getLocalLockState().disableLocking();
} catch (InterruptedException e) {
getLog().error("failed to disable the job store", e);
}
}
private LocalLockState getLocalLockState() {
LocalLockState rv = localStateLock;
if (rv != null) return rv;
synchronized (DefaultClusteredJobStore.class) {
if (localStateLock == null) {
localStateLock = new LocalLockState();
}
return localStateLock;
}
}
void lock() throws JobPersistenceException {
getLocalLockState().attemptAcquireBegin();
try {
lock.lock();
} catch (RejoinException e) {
getLocalLockState().release();
throw e;
}
}
void unlock() {
try {
lock.unlock();
} finally {
getLocalLockState().release();
}
}
/**
*
* Called by the QuartzScheduler before the JobStore
is used, in order to give the it a chance to
* initialize.
*
*/
@Override
// XXX: remove this suppression
@SuppressWarnings("unchecked")
public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler schedulerSignaler) {
this.terracottaClientId = clusterInfo.getCurrentNode().getId();
this.ftrCtr = System.currentTimeMillis();
// this MUST happen before initializing the trigger set (otherwise we might receive an update which get an NPE)
// this.serializer.setClassLoadHelper(loadHelper);
this.signaler = schedulerSignaler;
getLog().info(getClass().getSimpleName() + " initialized.");
((ToolkitInternal) toolkit).registerBeforeShutdownHook(new ShutdownHook(this));
}
@Override
public void schedulerStarted() throws SchedulerException {
clusterInfo.addClusterListener(this);
Collection nodes = clusterInfo.getNodes();
Set activeClientIDs = new HashSet();
for (ClusterNode node : nodes) {
boolean added = activeClientIDs.add(node.getId());
if (!added) {
getLog().error("DUPLICATE node ID detected: " + node);
}
}
lock();
try {
List toEval = new ArrayList();
// scan for orphaned triggers
for (TriggerKey triggerKey : triggerFacade.allTriggerKeys()) {
TriggerWrapper tw = triggerFacade.get(triggerKey);
String lastTerracotaClientId = tw.getLastTerracotaClientId();
if (lastTerracotaClientId == null) {
continue;
}
if (!activeClientIDs.contains(lastTerracotaClientId) || tw.getState() == TriggerState.ERROR) {
toEval.add(tw);
}
}
for (TriggerWrapper tw : toEval) {
evalOrphanedTrigger(tw, true);
}
// scan firedTriggers
for (Iterator iter = triggerFacade.allFiredTriggers().iterator(); iter.hasNext();) {
FiredTrigger ft = iter.next();
if (!activeClientIDs.contains(ft.getClientId())) {
getLog().info("Found non-complete fired trigger: " + ft);
iter.remove();
TriggerWrapper tw = triggerFacade.get(ft.getTriggerKey());
if (tw == null) {
getLog().error("no trigger found for executing trigger: " + ft.getTriggerKey());
continue;
}
scheduleRecoveryIfNeeded(tw, ft);
}
}
} finally {
unlock();
}
}
@Override
public void schedulerPaused() {
// do nothing
}
@Override
public void schedulerResumed() {
// do nothing
}
private void evalOrphanedTrigger(TriggerWrapper tw, boolean newNode) {
getLog().info("Evaluating orphaned trigger " + tw);
JobWrapper jobWrapper = jobFacade.get(tw.getJobKey());
if (jobWrapper == null) {
getLog().error("No job found for orphaned trigger: " + tw);
// even if it was deleted, there may be cleanup to do
jobFacade.removeBlockedJob(tw.getJobKey());
return;
}
if (newNode && tw.getState() == TriggerState.ERROR) {
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(tw);
}
if (tw.getState() == TriggerState.BLOCKED) {
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(tw);
} else if (tw.getState() == TriggerState.PAUSED_BLOCKED) {
tw.setState(TriggerState.PAUSED, terracottaClientId, triggerFacade);
}
if (tw.getState() == TriggerState.ACQUIRED) {
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(tw);
}
if (!tw.mayFireAgain() && !jobWrapper.requestsRecovery()) {
try {
removeTrigger(tw.getKey());
} catch (JobPersistenceException e) {
getLog().error("Can't remove completed trigger (and related job) " + tw, e);
}
}
if (jobWrapper.isConcurrentExectionDisallowed()) {
jobFacade.removeBlockedJob(jobWrapper.getKey());
List triggersForJob = triggerFacade.getTriggerWrappersForJob(jobWrapper.getKey());
for (TriggerWrapper trigger : triggersForJob) {
if (trigger.getState() == TriggerState.BLOCKED) {
trigger.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(trigger);
} else if (trigger.getState() == TriggerState.PAUSED_BLOCKED) {
trigger.setState(TriggerState.PAUSED, terracottaClientId, triggerFacade);
}
}
}
}
private void scheduleRecoveryIfNeeded(TriggerWrapper tw, FiredTrigger recovering) {
JobWrapper jobWrapper = jobFacade.get(tw.getJobKey());
if (jobWrapper == null) {
getLog().error("No job found for orphaned trigger: " + tw);
return;
}
if (jobWrapper.requestsRecovery()) {
OperableTrigger recoveryTrigger = createRecoveryTrigger(tw, jobWrapper, "recover_" + terracottaClientId + "_"
+ ftrCtr++, recovering);
JobDataMap jd = tw.getTriggerClone().getJobDataMap();
jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_NAME, tw.getKey().getName());
jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_GROUP, tw.getKey().getGroup());
jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_FIRETIME_IN_MILLISECONDS, String.valueOf(recovering.getFireTime()));
jd.put(Scheduler.FAILED_JOB_ORIGINAL_TRIGGER_SCHEDULED_FIRETIME_IN_MILLISECONDS, String.valueOf(recovering.getScheduledFireTime()));
recoveryTrigger.setJobDataMap(jd);
recoveryTrigger.computeFirstFireTime(null);
try {
storeTrigger(recoveryTrigger, false);
if (!tw.mayFireAgain()) {
removeTrigger(tw.getKey());
}
getLog().info("Recovered job " + jobWrapper + " for trigger " + tw);
} catch (JobPersistenceException e) {
getLog().error("Can't recover job " + jobWrapper + " for trigger " + tw, e);
}
}
}
protected OperableTrigger createRecoveryTrigger(TriggerWrapper tw, JobWrapper jw,
String name, FiredTrigger recovering) {
final SimpleTriggerImpl recoveryTrigger = new SimpleTriggerImpl(name, Scheduler.DEFAULT_RECOVERY_GROUP, new Date(recovering.getScheduledFireTime()));
recoveryTrigger.setJobName(jw.getKey().getName());
recoveryTrigger.setJobGroup(jw.getKey().getGroup());
recoveryTrigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY);
recoveryTrigger.setPriority(tw.getPriority());
return recoveryTrigger;
}
private 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
*/
@Override
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() {
// nothing to do
}
@Override
public boolean supportsPersistence() {
// We throw an assertion here since this method should never be called directly on this instance.
throw new AssertionError();
}
/**
*
* 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 {
lock();
try {
storeJob(newJob, false);
storeTrigger(newTrigger, false);
} finally {
unlock();
}
}
/**
*
* 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,
JobPersistenceException {
JobDetail clone = (JobDetail) newJob.clone();
lock();
try {
// wrapper construction must be done in lock since serializer is unlocked
JobWrapper jw = wrapperFactory.createJobWrapper(clone);
if (jobFacade.containsKey(jw.getKey())) {
if (!replaceExisting) { throw new ObjectAlreadyExistsException(newJob); }
} else {
// get job group
Set grpSet = toolkitDSHolder.getOrCreateJobsGroupMap(newJob.getKey().getGroup());
// add to jobs by group
grpSet.add(jw.getKey().getName());
if (!jobFacade.hasGroup(jw.getKey().getGroup())) {
jobFacade.addGroup(jw.getKey().getGroup());
}
}
// add/update jobs FQN map
jobFacade.put(jw.getKey(), jw);
} finally {
unlock();
}
}
/**
*
* Remove (delete) the {@link org.quartz.Job}
with the given name, and any
* {@link org.quartz.Trigger}
s that reference it.
*
*
* @param jobKey The key of the Job
to be removed.
* @return true
if a Job
with the given name & group was found and removed from the store.
*/
@Override
public boolean removeJob(JobKey jobKey) throws JobPersistenceException {
boolean found = false;
lock();
try {
List trigger = getTriggersForJob(jobKey);
for (OperableTrigger trig : trigger) {
this.removeTrigger(trig.getKey());
found = true;
}
found = (jobFacade.remove(jobKey) != null) | found;
if (found) {
Set grpSet = toolkitDSHolder.getOrCreateJobsGroupMap(jobKey.getGroup());
grpSet.remove(jobKey.getName());
if (grpSet.isEmpty()) {
toolkitDSHolder.removeJobsGroupMap(jobKey.getGroup());
jobFacade.removeGroup(jobKey.getGroup());
}
}
} finally {
unlock();
}
return found;
}
@Override
public boolean removeJobs(List jobKeys) throws JobPersistenceException {
boolean allFound = true;
lock();
try {
for (JobKey key : jobKeys)
allFound = removeJob(key) && allFound;
} finally {
unlock();
}
return allFound;
}
@Override
public boolean removeTriggers(List triggerKeys) throws JobPersistenceException {
boolean allFound = true;
lock();
try {
for (TriggerKey key : triggerKeys)
allFound = removeTrigger(key) && allFound;
} finally {
unlock();
}
return allFound;
}
@Override
public void storeJobsAndTriggers(Map> triggersAndJobs, boolean replace)
throws ObjectAlreadyExistsException, JobPersistenceException {
lock();
try {
// 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);
}
}
} finally {
unlock();
}
}
/**
*
* 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 #pauseTriggers(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public void storeTrigger(OperableTrigger newTrigger, boolean replaceExisting) throws JobPersistenceException {
OperableTrigger clone = (OperableTrigger) newTrigger.clone();
lock();
try {
JobDetail job = retrieveJob(newTrigger.getJobKey());
if (job == null) {
//
throw new JobPersistenceException("The job (" + newTrigger.getJobKey()
+ ") referenced by the trigger does not exist.");
}
// wrapper construction must be done in lock since serializer is unlocked
TriggerWrapper tw = wrapperFactory.createTriggerWrapper(clone, job.isConcurrentExectionDisallowed());
if (triggerFacade.containsKey(tw.getKey())) {
if (!replaceExisting) { throw new ObjectAlreadyExistsException(newTrigger); }
removeTrigger(newTrigger.getKey(), false);
}
// add to triggers by group
Set grpSet = toolkitDSHolder.getOrCreateTriggersGroupMap(newTrigger.getKey().getGroup());
grpSet.add(newTrigger.getKey().getName());
if (!triggerFacade.hasGroup(newTrigger.getKey().getGroup())) {
triggerFacade.addGroup(newTrigger.getKey().getGroup());
}
if (triggerFacade.pausedGroupsContain(newTrigger.getKey().getGroup())
|| jobFacade.pausedGroupsContain(newTrigger.getJobKey().getGroup())) {
tw.setState(TriggerState.PAUSED, terracottaClientId, triggerFacade);
if (jobFacade.blockedJobsContain(tw.getJobKey())) {
tw.setState(TriggerState.PAUSED_BLOCKED, terracottaClientId, triggerFacade);
}
} else if (jobFacade.blockedJobsContain(tw.getJobKey())) {
tw.setState(TriggerState.BLOCKED, terracottaClientId, triggerFacade);
} else {
timeTriggers.add(tw);
}
// add to triggers by FQN map
triggerFacade.put(tw.getKey(), tw);
} finally {
unlock();
}
}
/**
*
* Remove (delete) the {@link org.quartz.Trigger}
with the given name.
*
*
* @param triggerKey The key of the Trigger
to be removed.
* @return true
if a Trigger
with the given name & group was found and removed from the
* store.
*/
@Override
public boolean removeTrigger(TriggerKey triggerKey) throws JobPersistenceException {
return removeTrigger(triggerKey, true);
}
private boolean removeTrigger(TriggerKey triggerKey, boolean removeOrphanedJob) throws JobPersistenceException {
lock();
TriggerWrapper tw = null;
try {
// remove from triggers by FQN map
tw = triggerFacade.remove(triggerKey);
if (tw != null) {
// remove from triggers by group
Set grpSet = toolkitDSHolder.getOrCreateTriggersGroupMap(triggerKey.getGroup());
grpSet.remove(triggerKey.getName());
if (grpSet.size() == 0) {
toolkitDSHolder.removeTriggersGroupMap(triggerKey.getGroup());
triggerFacade.removeGroup(triggerKey.getGroup());
}
// remove from triggers array
timeTriggers.remove(tw);
if (removeOrphanedJob) {
JobWrapper jw = jobFacade.get(tw.getJobKey());
List trigs = getTriggersForJob(tw.getJobKey());
if ((trigs == null || trigs.size() == 0) && !jw.isDurable()) {
JobKey jobKey = tw.getJobKey();
if (removeJob(jobKey)) {
signaler.notifySchedulerListenersJobDeleted(jobKey);
}
}
}
}
} finally {
unlock();
}
return tw != null;
}
/**
* @see org.quartz.spi.JobStore#replaceTrigger
*/
@Override
public boolean replaceTrigger(TriggerKey triggerKey, OperableTrigger newTrigger) throws JobPersistenceException {
boolean found = false;
lock();
try {
// remove from triggers by FQN map
TriggerWrapper tw = triggerFacade.remove(triggerKey);
found = tw != null;
if (tw != null) {
if (!tw.getJobKey().equals(newTrigger.getJobKey())) { throw new JobPersistenceException(
"New trigger is not related to the same job as the old trigger."); }
// remove from triggers by group
Set grpSet = toolkitDSHolder.getOrCreateTriggersGroupMap(triggerKey.getGroup());
grpSet.remove(triggerKey.getName());
if (grpSet.size() == 0) {
toolkitDSHolder.removeTriggersGroupMap(triggerKey.getGroup());
triggerFacade.removeGroup(triggerKey.getGroup());
}
timeTriggers.remove(tw);
try {
storeTrigger(newTrigger, false);
} catch (JobPersistenceException jpe) {
storeTrigger(tw.getTriggerClone(), false); // put previous trigger back...
throw jpe;
}
}
} finally {
unlock();
}
return found;
}
/**
*
* Retrieve the {@link org.quartz.JobDetail}
for the given {@link org.quartz.Job}
.
*
*
* @param jobKey The key of the Job
to be retrieved.
* @return The desired Job
, or null if there is no match.
*/
@Override
public JobDetail retrieveJob(JobKey jobKey) throws JobPersistenceException {
JobWrapper jobWrapper = getJob(jobKey);
return jobWrapper == null ? null : (JobDetail) jobWrapper.getJobDetailClone();
}
JobWrapper getJob(final JobKey key) throws JobPersistenceException {
lock();
try {
return jobFacade.get(key);
} finally {
unlock();
}
}
/**
*
* Retrieve the given {@link org.quartz.Trigger}
.
*
*
* @param triggerKey The key of the Trigger
to be retrieved.
* @return The desired Trigger
, or null if there is no match.
*/
@Override
public OperableTrigger retrieveTrigger(TriggerKey triggerKey) throws JobPersistenceException {
lock();
try {
TriggerWrapper tw = triggerFacade.get(triggerKey);
return (tw != null) ? (OperableTrigger) tw.getTriggerClone() : null;
} finally {
unlock();
}
}
@Override
public boolean checkExists(final JobKey jobKey) {
return jobFacade.containsKey(jobKey);
}
/**
* {@inheritDoc}
*
* @throws JobPersistenceException
*/
@Override
public boolean checkExists(final TriggerKey triggerKey) throws JobPersistenceException {
return triggerFacade.containsKey(triggerKey);
}
@Override
public void clearAllSchedulingData() throws JobPersistenceException {
lock();
try {
// 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);
}
} finally {
unlock();
}
}
/**
*
* Get the current state of the identified {@link Trigger}
.
*
*
* @see Trigger.TriggerState
*/
@Override
public Trigger.TriggerState getTriggerState(org.quartz.TriggerKey key) throws JobPersistenceException {
TriggerWrapper tw;
lock();
try {
tw = triggerFacade.get(key);
} finally {
unlock();
}
if (tw == null) { return Trigger.TriggerState.NONE; }
if (tw.getState() == TriggerState.COMPLETE) { return Trigger.TriggerState.COMPLETE; }
if (tw.getState() == TriggerState.PAUSED) { return Trigger.TriggerState.PAUSED; }
if (tw.getState() == TriggerState.PAUSED_BLOCKED) { return Trigger.TriggerState.PAUSED; }
if (tw.getState() == TriggerState.BLOCKED) { return Trigger.TriggerState.BLOCKED; }
if (tw.getState() == TriggerState.ERROR) { return Trigger.TriggerState.ERROR; }
return Trigger.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, JobPersistenceException {
Calendar clone = (Calendar) calendar.clone();
lock();
try {
Calendar cal = calendarsByName.get(name);
if (cal != null && replaceExisting == false) {
throw new ObjectAlreadyExistsException("Calendar with name '" + name + "' already exists.");
} else if (cal != null) {
calendarsByName.remove(name);
}
Calendar cw = clone;
calendarsByName.putNoReturn(name, cw);
if (cal != null && updateTriggers) {
for (TriggerWrapper tw : triggerFacade.getTriggerWrappersForCalendar(name)) {
boolean removed = timeTriggers.remove(tw);
tw.updateWithNewCalendar(clone, getMisfireThreshold(), triggerFacade);
if (removed) {
timeTriggers.add(tw);
}
}
}
} finally {
unlock();
}
}
/**
*
* Remove (delete) the {@link org.quartz.Calendar}
with the given name.
*
*
* If removal of the Calendar
would result in 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;
lock();
try {
for (TriggerKey triggerKey : triggerFacade.allTriggerKeys()) {
TriggerWrapper tw = triggerFacade.get(triggerKey);
if (tw.getCalendarName() != null && tw.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);
} finally {
unlock();
}
}
/**
*
* 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) throws JobPersistenceException {
lock();
try {
Calendar cw = calendarsByName.get(calName);
return (Calendar) (cw == null ? null : cw.clone());
} finally {
unlock();
}
}
/**
*
* Get the number of {@link org.quartz.JobDetail}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfJobs() throws JobPersistenceException {
lock();
try {
return jobFacade.numberOfJobs();
} finally {
unlock();
}
}
/**
*
* Get the number of {@link org.quartz.Trigger}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfTriggers() throws JobPersistenceException {
lock();
try {
return triggerFacade.numberOfTriggers();
} finally {
unlock();
}
}
/**
*
* Get the number of {@link org.quartz.Calendar}
s that are stored in the JobsStore
.
*
*/
@Override
public int getNumberOfCalendars() throws JobPersistenceException {
lock();
try {
return calendarsByName.size();
} finally {
unlock();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Job}
s that have the given group name.
*
*/
@Override
public Set getJobKeys(GroupMatcher matcher) throws JobPersistenceException {
lock();
try {
Set matchingGroups = new HashSet();
switch (matcher.getCompareWithOperator()) {
case EQUALS:
matchingGroups.add(matcher.getCompareToValue());
break;
default:
for (String group : jobFacade.getAllGroupNames()) {
if (matcher.getCompareWithOperator().evaluate(group, matcher.getCompareToValue())) {
matchingGroups.add(group);
}
}
}
Set out = new HashSet();
for (String matchingGroup : matchingGroups) {
Set grpJobNames = toolkitDSHolder.getOrCreateJobsGroupMap(matchingGroup);
for (String jobName : grpJobNames) {
JobKey jobKey = new JobKey(jobName, matchingGroup);
if (jobFacade.containsKey(jobKey)) {
out.add(jobKey);
}
}
}
return out;
} finally {
unlock();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Calendar}
s in the JobStore
.
*
*
* If there are no Calendars in the given group name, the result should be a zero-length array (not null
* ).
*
*/
@Override
public List getCalendarNames() throws JobPersistenceException {
lock();
try {
Set names = calendarsByName.keySet();
return new ArrayList(names);
} finally {
unlock();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Trigger}
s that have the given group name.
*
*/
@Override
public Set getTriggerKeys(GroupMatcher matcher) throws JobPersistenceException {
lock();
try {
Set groupNames = new HashSet();
switch (matcher.getCompareWithOperator()) {
case EQUALS:
groupNames.add(matcher.getCompareToValue());
break;
default:
for (String group : triggerFacade.allTriggersGroupNames()) {
if (matcher.getCompareWithOperator().evaluate(group, matcher.getCompareToValue())) {
groupNames.add(group);
}
}
}
Set out = new HashSet();
for (String groupName : groupNames) {
Set grpSet = toolkitDSHolder.getOrCreateTriggersGroupMap(groupName);
for (String key : grpSet) {
TriggerKey triggerKey = new TriggerKey(key, groupName);
TriggerWrapper tw = triggerFacade.get(triggerKey);
if (tw != null) {
out.add(triggerKey);
}
}
}
return out;
} finally {
unlock();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Job}
groups.
*
*/
@Override
public List getJobGroupNames() throws JobPersistenceException {
lock();
try {
return new ArrayList(jobFacade.getAllGroupNames());
} finally {
unlock();
}
}
/**
*
* Get the names of all of the {@link org.quartz.Trigger}
groups.
*
*/
@Override
public List getTriggerGroupNames() throws JobPersistenceException {
lock();
try {
return new ArrayList(triggerFacade.allTriggersGroupNames());
} finally {
unlock();
}
}
/**
*
* 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(final JobKey jobKey) throws JobPersistenceException {
List trigList = new ArrayList();
lock();
try {
for (TriggerKey triggerKey : triggerFacade.allTriggerKeys()) {
TriggerWrapper tw = triggerFacade.get(triggerKey);
if (tw.getJobKey().equals(jobKey)) {
trigList.add(tw.getTriggerClone());
}
}
} finally {
unlock();
}
return trigList;
}
/**
*
* Pause the {@link Trigger}
with the given name.
*
*/
@Override
public void pauseTrigger(TriggerKey triggerKey) throws JobPersistenceException {
lock();
try {
TriggerWrapper tw = triggerFacade.get(triggerKey);
// does the trigger exist?
if (tw == null) { return; }
// if the trigger is "complete" pausing it does not make sense...
if (tw.getState() == TriggerState.COMPLETE) { return; }
if (tw.getState() == TriggerState.BLOCKED) {
tw.setState(TriggerState.PAUSED_BLOCKED, terracottaClientId, triggerFacade);
} else {
tw.setState(TriggerState.PAUSED, terracottaClientId, triggerFacade);
}
timeTriggers.remove(tw);
if (triggerRemovedFromCandidateFiringListHandler != null) {
triggerRemovedFromCandidateFiringListHandler.removeCandidateTrigger(tw);
}
} finally {
unlock();
}
}
/**
*
* Pause all of the {@link Trigger}s
in the given group.
*
*
* The JobStore should "remember" that the group is paused, and impose the pause on any new triggers that are added to
* the group while the group is paused.
*
*/
@Override
public Collection pauseTriggers(GroupMatcher matcher) throws JobPersistenceException {
HashSet pausedGroups = new HashSet();
lock();
try {
Set triggerKeys = getTriggerKeys(matcher);
for (TriggerKey key : triggerKeys) {
triggerFacade.addPausedGroup(key.getGroup());
pausedGroups.add(key.getGroup());
pauseTrigger(key);
}
// make sure to account for an exact group match for a group that doesn't yet exist
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
if (operator.equals(StringOperatorName.EQUALS)) {
triggerFacade.addPausedGroup(matcher.getCompareToValue());
pausedGroups.add(matcher.getCompareToValue());
}
} finally {
unlock();
}
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) throws JobPersistenceException {
lock();
try {
for (OperableTrigger trigger : getTriggersForJob(jobKey)) {
pauseTrigger(trigger.getKey());
}
} finally {
unlock();
}
}
/**
*
* 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 Collection pauseJobs(GroupMatcher matcher) throws JobPersistenceException {
Collection pausedGroups = new HashSet();
lock();
try {
Set jobKeys = getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
for (OperableTrigger trigger : getTriggersForJob(jobKey)) {
pauseTrigger(trigger.getKey());
}
pausedGroups.add(jobKey.getGroup());
}
// make sure to account for an exact group match for a group that doesn't yet exist
StringMatcher.StringOperatorName operator = matcher.getCompareWithOperator();
if (operator.equals(StringOperatorName.EQUALS)) {
jobFacade.addPausedGroup(matcher.getCompareToValue());
pausedGroups.add(matcher.getCompareToValue());
}
} finally {
unlock();
}
return pausedGroups;
}
/**
*
* Resume (un-pause) the {@link Trigger}
with the given name.
*
*
* If the Trigger
missed one or more fire-times, then the Trigger
's misfire instruction will
* be applied.
*
*/
@Override
public void resumeTrigger(TriggerKey triggerKey) throws JobPersistenceException {
lock();
try {
TriggerWrapper tw = triggerFacade.get(triggerKey);
// does the trigger exist?
if (tw == null) { return; }
// if the trigger is not paused resuming it does not make sense...
if (tw.getState() != TriggerState.PAUSED && tw.getState() != TriggerState.PAUSED_BLOCKED) { return; }
if (jobFacade.blockedJobsContain(tw.getJobKey())) {
tw.setState(TriggerState.BLOCKED, terracottaClientId, triggerFacade);
} else {
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
}
applyMisfire(tw);
if (tw.getState() == TriggerState.WAITING) {
timeTriggers.add(tw);
}
} finally {
unlock();
}
}
/**
*
* 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 Collection resumeTriggers(GroupMatcher matcher) throws JobPersistenceException {
Collection groups = new HashSet();
lock();
try {
Set triggerKeys = getTriggerKeys(matcher);
for (TriggerKey triggerKey : triggerKeys) {
TriggerWrapper tw = triggerFacade.get(triggerKey);
if (tw != null) {
String jobGroup = tw.getJobKey().getGroup();
if (jobFacade.pausedGroupsContain(jobGroup)) {
continue;
}
groups.add(triggerKey.getGroup());
}
resumeTrigger(triggerKey);
}
triggerFacade.removeAllPausedGroups(groups);
} finally {
unlock();
}
return 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) throws JobPersistenceException {
lock();
try {
for (OperableTrigger trigger : getTriggersForJob(jobKey)) {
resumeTrigger(trigger.getKey());
}
} finally {
unlock();
}
}
/**
*
* 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) throws JobPersistenceException {
Collection groups = new HashSet();
lock();
try {
Set jobKeys = getJobKeys(matcher);
for (JobKey jobKey : jobKeys) {
if (groups.add(jobKey.getGroup())) {
jobFacade.removePausedJobGroup(jobKey.getGroup());
}
for (OperableTrigger trigger : getTriggersForJob(jobKey)) {
resumeTrigger(trigger.getKey());
}
}
} finally {
unlock();
}
return groups;
}
/**
*
* Pause all triggers - equivalent of calling pauseTriggerGroup(group)
on every group.
*
*
* When resumeAll()
is called (to un-pause), trigger misfire instructions WILL be applied.
*
*
* @see #resumeAll()
* @see #pauseTriggers(org.quartz.impl.matchers.GroupMatcher)
*/
@Override
public void pauseAll() throws JobPersistenceException {
lock();
try {
List names = getTriggerGroupNames();
for (String name : names) {
pauseTriggers(GroupMatcher.triggerGroupEquals(name));
}
} finally {
unlock();
}
}
/**
*
* Resume (un-pause) all triggers - equivalent of calling resumeTriggerGroup(group)
on every group.
*
*
* If any Trigger
missed one or more fire-times, then the Trigger
's misfire instruction will
* be applied.
*
*
* @see #pauseAll()
*/
@Override
public void resumeAll() throws JobPersistenceException {
lock();
try {
jobFacade.clearPausedJobGroups();
List names = getTriggerGroupNames();
for (String name : names) {
resumeTriggers(GroupMatcher.triggerGroupEquals(name));
}
} finally {
unlock();
}
}
boolean applyMisfire(TriggerWrapper tw) throws JobPersistenceException {
long misfireTime = System.currentTimeMillis();
if (getMisfireThreshold() > 0) {
misfireTime -= getMisfireThreshold();
}
Date tnft = tw.getNextFireTime();
if (tnft == null || tnft.getTime() > misfireTime
|| tw.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { return false; }
Calendar cal = null;
if (tw.getCalendarName() != null) {
cal = retrieveCalendar(tw.getCalendarName());
}
signaler.notifyTriggerListenersMisfired(tw.getTriggerClone());
tw.updateAfterMisfire(cal, triggerFacade);
if (tw.getNextFireTime() == null) {
tw.setState(TriggerState.COMPLETE, terracottaClientId, triggerFacade);
signaler.notifySchedulerListenersFinalized(tw.getTriggerClone());
timeTriggers.remove(tw);
} else if (tnft.equals(tw.getNextFireTime())) { return false; }
return true;
}
@Override
public List acquireNextTriggers(long noLaterThan, int maxCount, long timeWindow)
throws JobPersistenceException {
List result = new ArrayList();;
lock();
try {
for (TriggerWrapper tw : getNextTriggerWrappers(timeTriggers, noLaterThan, maxCount, timeWindow)) {
result.add(markAndCloneTrigger(tw));
}
return result;
} finally {
try {
unlock();
} catch (RejoinException e) {
if (!validateAcquired(result)) {
throw e;
}
}
}
}
private boolean validateAcquired(List result) {
if (result.isEmpty()) {
return false;
} else {
while (!toolkitShutdown) {
try {
lock();
try {
for (OperableTrigger ot : result) {
TriggerWrapper tw = triggerFacade.get(ot.getKey());
if (!ot.getFireInstanceId().equals(tw.getTriggerClone().getFireInstanceId()) || !TriggerState.ACQUIRED.equals(tw.getState())) {
return false;
}
}
return true;
} finally {
unlock();
}
} catch (JobPersistenceException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
} catch (RejoinException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
}
}
throw new IllegalStateException("Scheduler has been shutdown");
}
}
OperableTrigger markAndCloneTrigger(final TriggerWrapper tw) {
tw.setState(TriggerState.ACQUIRED, terracottaClientId, triggerFacade);
String firedInstanceId = terracottaClientId + "-" + String.valueOf(ftrCtr++);
tw.setFireInstanceId(firedInstanceId, triggerFacade);
return tw.getTriggerClone();
}
List getNextTriggerWrappers(final long noLaterThan, final int maxCount, final long timeWindow)
throws JobPersistenceException {
return getNextTriggerWrappers(timeTriggers, noLaterThan, maxCount, timeWindow);
}
List getNextTriggerWrappers(final TimeTriggerSet source, final long noLaterThan, final int maxCount,
final long timeWindow) throws JobPersistenceException {
List wrappers = new ArrayList();
Set acquiredJobKeysForNoConcurrentExec = new HashSet();
Set excludedTriggers = new HashSet();
JobPersistenceException caughtJpe = null;
long firstAcquiredTriggerFireTime = 0;
try {
while (true) {
TriggerWrapper tw = null;
try {
TriggerKey triggerKey = source.removeFirst();
if (triggerKey != null) {
tw = triggerFacade.get(triggerKey);
}
if (tw == null) break;
} catch (java.util.NoSuchElementException nsee) {
break;
}
if (tw.getNextFireTime() == null) {
continue;
}
// it's possible that we've selected triggers way outside of the max fire ahead time for batches
// (up to idleWaitTime + fireAheadTime) so we need to make sure not to include such triggers.
// So we select from the first next trigger to fire up until the max fire ahead time after that...
// which will perfectly honor the fireAheadTime window because the no firing will occur until
// the first acquired trigger's fire time arrives.
if (firstAcquiredTriggerFireTime > 0
&& tw.getNextFireTime().getTime() > (firstAcquiredTriggerFireTime + timeWindow)) {
source.add(tw);
break;
}
if (applyMisfire(tw)) {
if (tw.getNextFireTime() != null) {
source.add(tw);
}
continue;
}
if (tw.getNextFireTime().getTime() > noLaterThan + timeWindow) {
source.add(tw);
break;
}
if (tw.jobDisallowsConcurrence()) {
if (acquiredJobKeysForNoConcurrentExec.contains(tw.getJobKey())) {
excludedTriggers.add(tw);
continue;
}
acquiredJobKeysForNoConcurrentExec.add(tw.getJobKey());
}
wrappers.add(tw);
if (firstAcquiredTriggerFireTime == 0) firstAcquiredTriggerFireTime = tw.getNextFireTime().getTime();
if (wrappers.size() == maxCount) {
break;
}
}
} catch (JobPersistenceException jpe) {
caughtJpe = jpe; // hold the exception while we patch back up the collection ...
}
// If we did excluded triggers to prevent ACQUIRE state due to DisallowConcurrentExecution, we need to add them back
// to store.
if (excludedTriggers.size() > 0) {
for (TriggerWrapper tw : excludedTriggers) {
source.add(tw);
}
}
// if we held and exception, now we need to put back all the TriggerWrappers that we may have removed from the
// source set
if (caughtJpe != null) {
for (TriggerWrapper tw : wrappers) {
source.add(tw);
}
// and now throw the exception...
throw new JobPersistenceException("Exception encountered while trying to select triggers for firing.", caughtJpe);
}
return wrappers;
}
public void setTriggerRemovedFromCandidateFiringListHandler(final TriggerRemovedFromCandidateFiringListHandler triggerRemovedFromCandidateFiringListHandler) {
this.triggerRemovedFromCandidateFiringListHandler = triggerRemovedFromCandidateFiringListHandler;
}
/**
*
* 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) {
while (!toolkitShutdown) {
try {
lock();
try {
TriggerWrapper tw = triggerFacade.get(trigger.getKey());
if (tw != null && trigger.getFireInstanceId().equals(tw.getTriggerClone().getFireInstanceId()) && tw.getState() == TriggerState.ACQUIRED) {
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(tw);
}
} finally {
unlock();
}
} catch (RejoinException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
} catch (JobPersistenceException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
}
break;
}
}
/**
*
* 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 triggersFired) throws JobPersistenceException {
List results = new ArrayList();
lock();
try {
for (OperableTrigger trigger : triggersFired) {
TriggerWrapper tw = triggerFacade.get(trigger.getKey());
// was the trigger deleted since being acquired?
if (tw == null) {
results.add(new TriggerFiredResult((TriggerFiredBundle) null));
continue;
}
// was the trigger completed, paused, blocked, etc. since being acquired?
if (tw.getState() != TriggerState.ACQUIRED) {
results.add(new TriggerFiredResult((TriggerFiredBundle) null));
continue;
}
Calendar cal = null;
if (tw.getCalendarName() != null) {
cal = retrieveCalendar(tw.getCalendarName());
if (cal == null) {
results.add(new TriggerFiredResult((TriggerFiredBundle) null));
continue;
}
}
Date prevFireTime = trigger.getPreviousFireTime();
// in case trigger was replaced between acquiring and firering
timeTriggers.remove(tw);
// call triggered on our copy, and the scheduler's copy
tw.triggered(cal, triggerFacade);
trigger.triggered(cal); // calendar is already clone()'d so it is okay to pass out to trigger
// tw.state = EXECUTING;
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
TriggerFiredBundle bndle = new TriggerFiredBundle(retrieveJob(trigger.getJobKey()), trigger, cal, false,
new Date(), trigger.getPreviousFireTime(), prevFireTime,
trigger.getNextFireTime());
String fireInstanceId = trigger.getFireInstanceId();
FiredTrigger prev = triggerFacade.getFiredTrigger(fireInstanceId);
triggerFacade.putFiredTrigger(fireInstanceId, new FiredTrigger(terracottaClientId, tw.getKey(), trigger.getPreviousFireTime().getTime()));
getLog().trace("Tracking " + trigger + " has fired on " + fireInstanceId);
if (prev != null) {
// this shouldn't happen
throw new AssertionError("duplicate fireInstanceId detected (" + fireInstanceId + ") for " + trigger
+ ", previous is " + prev);
}
JobDetail job = bndle.getJobDetail();
if (job.isConcurrentExectionDisallowed()) {
List trigs = triggerFacade.getTriggerWrappersForJob(job.getKey());
for (TriggerWrapper ttw : trigs) {
if (ttw.getKey().equals(tw.getKey())) {
continue;
}
if (ttw.getState() == TriggerState.WAITING) {
ttw.setState(TriggerState.BLOCKED, terracottaClientId, triggerFacade);
}
if (ttw.getState() == TriggerState.PAUSED) {
ttw.setState(TriggerState.PAUSED_BLOCKED, terracottaClientId, triggerFacade);
}
timeTriggers.remove(ttw);
if (triggerRemovedFromCandidateFiringListHandler != null) {
triggerRemovedFromCandidateFiringListHandler.removeCandidateTrigger(ttw);
}
}
jobFacade.addBlockedJob(job.getKey());
} else if (tw.getNextFireTime() != null) {
timeTriggers.add(tw);
}
results.add(new TriggerFiredResult(bndle));
}
return results;
} finally {
try {
unlock();
} catch (RejoinException e) {
if (!validateFiring(results)) {
throw e;
}
}
}
}
private boolean validateFiring(List result) {
if (result.isEmpty()) {
return false;
} else {
while (!toolkitShutdown) {
try {
lock();
try {
for (TriggerFiredResult tfr : result) {
TriggerFiredBundle tfb = tfr.getTriggerFiredBundle();
if (tfb != null && !triggerFacade.containsFiredTrigger(tfb.getTrigger().getFireInstanceId())) {
return false;
}
}
return true;
} finally {
unlock();
}
} catch (JobPersistenceException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
} catch (RejoinException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
}
}
throw new IllegalStateException("Scheduler has been shutdown");
}
}
/**
*
* 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) {
while (!toolkitShutdown) {
try {
lock();
try {
String fireId = trigger.getFireInstanceId();
FiredTrigger removed = triggerFacade.removeFiredTrigger(fireId);
if (removed == null) {
getLog().warn("No fired trigger record found for " + trigger + " (" + fireId + ")");
break;
}
JobKey jobKey = jobDetail.getKey();
JobWrapper jw = jobFacade.get(jobKey);
TriggerWrapper tw = triggerFacade.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) {
if (jw.isPersistJobDataAfterExecution()) {
JobDataMap newData = jobDetail.getJobDataMap();
if (newData != null) {
newData = (JobDataMap) newData.clone();
newData.clearDirtyFlag();
}
jw.setJobDataMap(newData, jobFacade);
}
if (jw.isConcurrentExectionDisallowed()) {
jobFacade.removeBlockedJob(jw.getKey());
tw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(tw);
List trigs = triggerFacade.getTriggerWrappersForJob(jw.getKey());
for (TriggerWrapper ttw : trigs) {
if (ttw.getState() == TriggerState.BLOCKED) {
ttw.setState(TriggerState.WAITING, terracottaClientId, triggerFacade);
timeTriggers.add(ttw);
}
if (ttw.getState() == TriggerState.PAUSED_BLOCKED) {
ttw.setState(TriggerState.PAUSED, terracottaClientId, triggerFacade);
}
}
signaler.signalSchedulingChange(0L);
}
} else { // even if it was deleted, there may be cleanup to do
jobFacade.removeBlockedJob(jobKey);
}
// 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.getNextFireTime() == null) {
removeTrigger(trigger.getKey());
}
} else {
removeTrigger(trigger.getKey());
signaler.signalSchedulingChange(0L);
}
} else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_COMPLETE) {
tw.setState(TriggerState.COMPLETE, terracottaClientId, triggerFacade);
timeTriggers.remove(tw);
signaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_TRIGGER_ERROR) {
getLog().info("Trigger " + trigger.getKey() + " set to ERROR state.");
tw.setState(TriggerState.ERROR, terracottaClientId, triggerFacade);
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(), TriggerState.ERROR);
signaler.signalSchedulingChange(0L);
} else if (triggerInstCode == CompletedExecutionInstruction.SET_ALL_JOB_TRIGGERS_COMPLETE) {
setAllTriggersOfJobToState(trigger.getJobKey(), TriggerState.COMPLETE);
signaler.signalSchedulingChange(0L);
}
}
} finally {
unlock();
}
} catch (RejoinException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
} catch (JobPersistenceException e) {
try {
Thread.sleep(retryInterval);
} catch (InterruptedException f) {
throw new IllegalStateException("Received interrupted exception", f);
}
continue;
}
break;
}
}
private void setAllTriggersOfJobToState(JobKey jobKey, TriggerState state) {
List tws = triggerFacade.getTriggerWrappersForJob(jobKey);
for (TriggerWrapper tw : tws) {
tw.setState(state, terracottaClientId, triggerFacade);
if (state != TriggerState.WAITING) {
timeTriggers.remove(tw);
}
}
}
/**
* @see org.quartz.spi.JobStore#getPausedTriggerGroups()
*/
@Override
public Set getPausedTriggerGroups() throws JobPersistenceException {
lock();
try {
return new HashSet(triggerFacade.allPausedTriggersGroupNames());
} finally {
unlock();
}
}
@Override
public void setInstanceId(String schedInstId) {
//
}
@Override
public void setInstanceName(String schedName) {
//
}
@Override
public void setTcRetryInterval(long retryInterval) {
this.retryInterval = retryInterval;
}
public void nodeLeft(ClusterEvent event) {
final String nodeLeft = event.getNode().getId();
try {
lock();
} catch (JobPersistenceException e) {
getLog().info("Job store is already disabled, not processing nodeLeft() for " + nodeLeft);
return;
}
try {
List toEval = new ArrayList();
for (TriggerKey triggerKey : triggerFacade.allTriggerKeys()) {
TriggerWrapper tw = triggerFacade.get(triggerKey);
String clientId = tw.getLastTerracotaClientId();
if (clientId != null && clientId.equals(nodeLeft)) {
toEval.add(tw);
}
}
for (TriggerWrapper tw : toEval) {
evalOrphanedTrigger(tw, false);
}
for (Iterator iter = triggerFacade.allFiredTriggers().iterator(); iter.hasNext();) {
FiredTrigger ft = iter.next();
if (nodeLeft.equals(ft.getClientId())) {
getLog().info("Found non-complete fired trigger: " + ft);
iter.remove();
TriggerWrapper tw = triggerFacade.get(ft.getTriggerKey());
if (tw == null) {
getLog().error("no trigger found for executing trigger: " + ft.getTriggerKey());
continue;
}
scheduleRecoveryIfNeeded(tw, ft);
}
}
} finally {
unlock();
}
// nudge the local scheduler. This is a lazy way to do it. This should perhaps be conditionally happening and
// also passing a real next job time (as opposed to 0)
signaler.signalSchedulingChange(0);
}
@Override
public long getEstimatedTimeToReleaseAndAcquireTrigger() {
// right now this is a static (but configurable) value. It could be based on actual observation
// of trigger acquire/release at runtime in the future though
return this.estimatedTimeToReleaseAndAcquireTrigger;
}
@Override
public void setEstimatedTimeToReleaseAndAcquireTrigger(long estimate) {
this.estimatedTimeToReleaseAndAcquireTrigger = estimate;
}
@Override
public void setThreadPoolSize(final int size) {
//
}
@Override
public boolean isClustered() {
// We throw an assertion here since this method should never be called directly on this instance.
throw new AssertionError();
}
void injectTriggerWrapper(final TriggerWrapper triggerWrapper) {
timeTriggers.add(triggerWrapper);
}
private static class ShutdownHook implements Runnable {
private final DefaultClusteredJobStore store;
ShutdownHook(DefaultClusteredJobStore store) {
this.store = store;
}
@Override
public void run() {
store.disable();
}
}
private static class LocalLockState {
private int acquires = 0;
private boolean disabled;
synchronized void attemptAcquireBegin() throws JobPersistenceException {
if (disabled) { throw new JobPersistenceException("org.terracotta.quartz.TerracottaJobStore is disabled"); }
acquires++;
}
synchronized void release() {
acquires--;
notifyAll();
}
synchronized void disableLocking() throws InterruptedException {
disabled = true;
while (acquires > 0) {
wait();
}
}
}
ClusterInfo getDsoCluster() {
return clusterInfo;
}
interface TriggerRemovedFromCandidateFiringListHandler {
public boolean removeCandidateTrigger(final TriggerWrapper ttw);
}
@Override
public void onClusterEvent(ClusterEvent event) {
switch (event.getType()) {
case NODE_JOINED:
case OPERATIONS_DISABLED:
case OPERATIONS_ENABLED:
break;
case NODE_LEFT:
getLog().info("Received node left notification for " + event.getNode().getId());
nodeLeft(event);
break;
case NODE_REJOINED:
getLog().info("Received rejoin notification " + terracottaClientId + " => " + event.getNode().getId());
terracottaClientId = event.getNode().getId();
break;
}
}
protected TriggerFacade getTriggersFacade() {
return this.triggerFacade;
}
}