de.ingrid.iplug.scheduler.FileJobStore.java Source code

Java tutorial

Introduction

Here is the source code for de.ingrid.iplug.scheduler.FileJobStore.java

Source

/*
 * **************************************************-
 * ingrid-iplug
 * ==================================================
 * Copyright (C) 2014 - 2016 wemove digital solutions GmbH
 * ==================================================
 * Licensed under the EUPL, Version 1.1 or  as soon they will be
 * approved by the European Commission - subsequent versions of the
 * EUPL (the "Licence");
 * 
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 * http://ec.europa.eu/idabc/eupl5
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 * **************************************************#
 */
/*
 * Copyright (c) 1997-2005 by media style GmbH
 * 
 * $Source: /cvs/asp-search/src/java/com/ms/aspsearch/PermissionDeniedException.java,v $
 */

package de.ingrid.iplug.scheduler;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Calendar;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobPersistenceException;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.SchedulerConfigException;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.core.SchedulingContext;
import org.quartz.spi.ClassLoadHelper;
import org.quartz.spi.JobStore;
import org.quartz.spi.SchedulerSignaler;
import org.quartz.spi.TriggerFiredBundle;

/**
 * 
 * This class implements a <code>{@link org.quartz.spi.JobStore}</code> that utilizes the FileSystem as its storage
 * device.
 * 
 * <p/>created on 11.10.2005
 * 
 * @version $Revision: $
 * @author jz
 * @author $ Author: jz ${lastedit}
 */
public class FileJobStore implements JobStore {

    private static Log log = LogFactory.getLog(FileJobStore.class);

    // persistent fields
    private HashMap fJobsByName;

    private HashMap fTriggersByName = new HashMap();

    private HashMap fCalendarsByName = new HashMap();

    private HashMap fTriggerStatesByName = new HashMap();

    private HashSet fPausedTriggerGroups = new HashSet();

    // transient fields
    private SchedulerSignaler fSignaler;

    private long fMisfireThreshold = 5001;

    private FileJobStoreSerializer fSerializer;

    private String fStoreDirectory = "quartzstore";

    private HashMap fTriggersByGroup = new HashMap();

    private HashMap fJobsByGroup = new HashMap();

    private TreeSet fOrderedTriggers = new TreeSet();

    private HashSet fBlockedJobs = new HashSet();

    public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
        this.fSignaler = signaler;
        try {
            this.fSerializer = new FileJobStoreSerializer(this.fStoreDirectory);
            initPeristentFields();
        } catch (JobPersistenceException e) {
            throw new SchedulerConfigException("could not load persistent data ", e);
        }
        fillTransientFields();
        log.info("FileJobStore initialized : " + this.fSerializer.getStoreDirectory().getAbsolutePath());
    }

    public void schedulerStarted() throws SchedulerException {
        // nothing to do
    }

    public void shutdown() {
        // nothing to do
    }

    public boolean supportsPersistence() {
        return true;
    }

    public void storeJobAndTrigger(SchedulingContext ctxt, JobDetail newJob, Trigger newTrigger)
            throws ObjectAlreadyExistsException, JobPersistenceException {
        storeJob(ctxt, newJob, false);
        storeTrigger(ctxt, newTrigger, false);

    }

    public void storeJob(SchedulingContext ctxt, JobDetail newJobDetail, boolean replaceExisting)
            throws ObjectAlreadyExistsException, JobPersistenceException {
        boolean jobExists = this.fJobsByName.containsKey(newJobDetail.getFullName());
        if (!replaceExisting && jobExists) {
            throw new ObjectAlreadyExistsException(newJobDetail);
        } else if (replaceExisting && jobExists) {
            log.warn("overwriting existing job: " + newJobDetail.getFullName());
        }

        synchronized (this.fJobsByName) {
            if (!jobExists) {
                addJobGroup(newJobDetail);
            }
            this.fJobsByName.put(newJobDetail.getFullName(), newJobDetail);
            this.fSerializer.saveJobs(this.fJobsByName);
        }
    }

    private void addJobGroup(JobDetail newJob) {
        HashMap groupMap = (HashMap) this.fJobsByGroup.get(newJob.getGroup());
        if (groupMap == null) {
            groupMap = new HashMap();
            this.fJobsByGroup.put(newJob.getGroup(), groupMap);
        }
        groupMap.put(newJob.getName(), newJob);
    }

    private static String getFullName(String group, String name) {
        StringBuffer buffer = new StringBuffer(group);
        buffer.append(".");
        buffer.append(name);
        return buffer.toString();
    }

    public boolean removeJob(SchedulingContext ctxt, String jobName, String groupName)
            throws JobPersistenceException {
        JobDetail job = (JobDetail) this.fJobsByName.get(getFullName(groupName, jobName));
        if (job == null) {
            return false;
        }

        Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName);
        for (int i = 0; i < triggers.length; i++) {
            removeTrigger(ctxt, triggers[i].getName(), triggers[i].getGroup());
        }

        synchronized (this.fJobsByName) {
            HashMap groupMap = (HashMap) this.fJobsByGroup.get(groupName);
            if (groupMap != null) {
                groupMap.remove(jobName);
                if (groupMap.size() == 0)
                    this.fJobsByGroup.remove(groupName);
            }
            this.fJobsByName.remove(job.getFullName());
            this.fBlockedJobs.remove(job);
            this.fSerializer.saveJobs(this.fJobsByName);
        }

        return true;
    }

    public JobDetail retrieveJob(SchedulingContext ctxt, String jobName, String groupName)
            throws JobPersistenceException {
        return (JobDetail) this.fJobsByName.get(getFullName(groupName, jobName));
    }

    public void storeTrigger(SchedulingContext ctxt, Trigger newTrigger, boolean replaceExisting)
            throws ObjectAlreadyExistsException, JobPersistenceException {
        if (this.fTriggersByName.containsKey(newTrigger.getFullName())) {
            if (!replaceExisting) {
                throw new ObjectAlreadyExistsException(newTrigger);
            }
            log.warn("overwriting existing trigger: " + newTrigger.getFullName());
            removeTrigger(ctxt, newTrigger.getName(), newTrigger.getGroup());
        }

        if (!this.fJobsByName.containsKey(newTrigger.getFullJobName())) {
            throw new JobPersistenceException(
                    "The job (" + newTrigger.getFullJobName() + ") referenced by the trigger does not exist.");
        }

        synchronized (this.fTriggersByName) {
            addTriggerGroup(newTrigger);
            this.fTriggersByName.put(newTrigger.getFullName(), newTrigger);
            this.fSerializer.saveTriggers(this.fTriggersByName);
            synchronized (this.fPausedTriggerGroups) {
                if (this.fPausedTriggerGroups.contains(newTrigger.getGroup())) {
                    setTriggerState(ctxt, newTrigger, Trigger.STATE_PAUSED);
                } else {
                    this.fOrderedTriggers.add(newTrigger);
                }
            }
        }
    }

    private void addTriggerGroup(Trigger newTrigger) {
        HashMap groupMap = (HashMap) this.fTriggersByGroup.get(newTrigger.getGroup());
        if (groupMap == null) {
            groupMap = new HashMap();
            this.fTriggersByGroup.put(newTrigger.getGroup(), groupMap);
        }
        groupMap.put(newTrigger.getName(), newTrigger);
    }

    public boolean removeTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws JobPersistenceException {
        synchronized (this.fTriggersByName) {
            // remove from triggers map
            Trigger trigger = (Trigger) this.fTriggersByName.remove(getFullName(groupName, triggerName));
            if (trigger == null) {
                return false;
            }
            this.fSerializer.saveTriggers(this.fTriggersByName);

            // remove from triggers by group
            HashMap grpMap = (HashMap) this.fTriggersByGroup.get(groupName);
            if (grpMap != null) {
                grpMap.remove(triggerName);
                if (grpMap.size() == 0)
                    this.fTriggersByGroup.remove(groupName);
            }
            this.fOrderedTriggers.remove(trigger);
            JobDetail jobDetail = retrieveJob(ctxt, trigger.getJobName(), trigger.getJobGroup());

            // remove state
            setTriggerState(ctxt, trigger, Trigger.STATE_NONE);

            if (!jobDetail.isDurable()
                    && getTriggersForJob(ctxt, jobDetail.getName(), jobDetail.getGroup()).length == 0) {
                removeJob(ctxt, jobDetail.getName(), jobDetail.getGroup());
            }
        }

        return true;
    }

    public boolean replaceTrigger(SchedulingContext ctxt, String triggerName, String groupName, Trigger newTrigger)
            throws JobPersistenceException {
        Trigger oldTrigger = retrieveTrigger(ctxt, triggerName, groupName);
        if (oldTrigger == null)
            return false;
        if (!oldTrigger.getJobName().equals(newTrigger.getJobName())
                || !oldTrigger.getJobGroup().equals(newTrigger.getJobGroup()))
            throw new JobPersistenceException("New trigger is not related to the same job as the old trigger.");

        JobDetail jobDetail = retrieveJob(ctxt, oldTrigger.getJobName(), oldTrigger.getJobGroup());
        synchronized (this.fTriggersByName) {
            removeTrigger(ctxt, triggerName, groupName);
            if (!this.fJobsByName.containsKey(oldTrigger.getFullJobName())) {
                storeJob(ctxt, jobDetail, false);
            }
            storeTrigger(ctxt, newTrigger, false);
        }
        return true;
    }

    public Trigger retrieveTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws JobPersistenceException {
        return (Trigger) this.fTriggersByName.get(getFullName(groupName, triggerName));
    }

    public void storeCalendar(SchedulingContext ctxt, String name, Calendar calendar, boolean replaceExisting,
            boolean updateTriggers) throws ObjectAlreadyExistsException, JobPersistenceException {

        boolean calenderExists = this.fCalendarsByName.containsKey(name);
        if (calenderExists) {
            if (!replaceExisting) {
                throw new ObjectAlreadyExistsException("Calendar with name '" + name + "' already exists.");
            }
            this.fCalendarsByName.remove(name);
        }
        this.fCalendarsByName.put(name, calendar);
        this.fSerializer.saveCalendars(this.fCalendarsByName);

        // update triggers
        if (calenderExists && updateTriggers) {
            synchronized (this.fTriggersByName) {
                Trigger[] triggers = getTriggerForCalendar(name);
                for (int i = 0; i < triggers.length; i++) {
                    boolean removed = this.fOrderedTriggers.remove(triggers[i]);
                    triggers[i].updateWithNewCalendar(calendar, getMisfireThreshold());
                    if (removed) {
                        this.fOrderedTriggers.add(triggers[i]);
                    }
                }
            }
        }
    }

    public boolean removeCalendar(SchedulingContext ctxt, String calName) throws JobPersistenceException {
        if (getTriggerForCalendar(calName).length > 0) {
            throw new JobPersistenceException("Calender cannot be removed if it referenced by a Trigger!");
        }

        boolean exists = this.fCalendarsByName.remove(calName) != null;
        if (exists) {
            this.fSerializer.saveCalendars(this.fCalendarsByName);
        }
        return exists;
    }

    public Calendar retrieveCalendar(SchedulingContext ctxt, String calName) throws JobPersistenceException {
        return (Calendar) this.fCalendarsByName.get(calName);
    }

    public int getNumberOfJobs(SchedulingContext ctxt) throws JobPersistenceException {
        return this.fJobsByName.size();
    }

    public int getNumberOfTriggers(SchedulingContext ctxt) throws JobPersistenceException {
        return this.fTriggersByName.size();
    }

    public int getNumberOfCalendars(SchedulingContext ctxt) throws JobPersistenceException {
        return this.fCalendarsByName.size();
    }

    public String[] getJobNames(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        HashMap groupMap = (HashMap) this.fJobsByGroup.get(groupName);
        if (groupMap == null)
            return new String[0];

        return (String[]) groupMap.keySet().toArray(new String[groupMap.keySet().size()]);
    }

    public String[] getTriggerNames(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        HashMap groupMap = (HashMap) this.fTriggersByGroup.get(groupName);
        if (groupMap == null)
            return new String[0];

        return (String[]) groupMap.keySet().toArray(new String[groupMap.keySet().size()]);
    }

    public String[] getJobGroupNames(SchedulingContext ctxt) throws JobPersistenceException {
        return (String[]) this.fJobsByGroup.keySet().toArray(new String[this.fJobsByGroup.keySet().size()]);
    }

    public String[] getTriggerGroupNames(SchedulingContext ctxt) throws JobPersistenceException {
        return (String[]) this.fTriggersByGroup.keySet().toArray(new String[this.fTriggersByGroup.keySet().size()]);
    }

    public String[] getCalendarNames(SchedulingContext ctxt) throws JobPersistenceException {
        return (String[]) this.fCalendarsByName.keySet().toArray(new String[this.fCalendarsByName.keySet().size()]);
    }

    public Trigger[] getTriggersForJob(SchedulingContext ctxt, String jobName, String groupName)
            throws JobPersistenceException {
        String jobFullName = getFullName(groupName, jobName);
        ArrayList triggers = new ArrayList();
        synchronized (this.fTriggersByName) {
            for (Iterator iter = this.fTriggersByName.values().iterator(); iter.hasNext();) {
                Trigger trigger = (Trigger) iter.next();
                if (trigger.getFullJobName().equals(jobFullName))
                    triggers.add(trigger);
            }
        }
        return (Trigger[]) triggers.toArray(new Trigger[triggers.size()]);
    }

    private Trigger[] getTriggerForCalendar(String calName) {
        ArrayList triggers = new ArrayList();
        synchronized (this.fTriggersByName) {
            for (Iterator iter = this.fTriggersByName.values().iterator(); iter.hasNext();) {
                Trigger trigger = (Trigger) iter.next();
                if (calName.equals(trigger.getCalendarName())) {
                    triggers.add(trigger);
                }
            }
        }

        return (Trigger[]) triggers.toArray(new Trigger[triggers.size()]);
    }

    /**
     * Return a trigger state.
     * 
     * STATE_NONE - not in TriggersByName .................................
    * STATE_NORMAL - not in TriggerStatesByName ..........................
     * STATE_COMPLETE - with int value in TriggerStatesByName .............
     * STATE_ERROR - with int value in TriggerStatesByName ................
     * STATE_PAUSED - with int value in TriggerStatesByName ...............
     * STATE_BLOCKED - has job in BlockedJobs .............................
     * @return The trigger state.
     */
    public int getTriggerState(SchedulingContext ctxt, String triggerName, String triggerGroup)
            throws JobPersistenceException {
        Trigger trigger = retrieveTrigger(ctxt, triggerName, triggerGroup);
        if (trigger == null)
            return Trigger.STATE_NONE;

        Integer triggerState = (Integer) this.fTriggerStatesByName.get(trigger.getFullName());
        boolean jobBlocked = this.fBlockedJobs.contains(trigger.getFullJobName());
        if (jobBlocked) {
            if (triggerState == null || triggerState.intValue() != Trigger.STATE_PAUSED)
                return Trigger.STATE_BLOCKED;
        }

        if (triggerState == null)
            return Trigger.STATE_NORMAL;

        return triggerState.intValue();
    }

    private void setTriggerState(SchedulingContext ctxt, Trigger trigger, int state)
            throws JobPersistenceException {
        int oldState = getTriggerState(null, trigger.getName(), trigger.getGroup());
        if (oldState == Trigger.STATE_COMPLETE && oldState != Trigger.STATE_NONE)
            return;

        switch (state) {
        case Trigger.STATE_NONE:
            this.fTriggerStatesByName.remove(trigger.getFullName());
            this.fSerializer.saveTriggerStates(this.fTriggerStatesByName);
            break;
        case Trigger.STATE_NORMAL:
            this.fTriggerStatesByName.remove(trigger.getFullName());
            this.fSerializer.saveTriggerStates(this.fTriggerStatesByName);
            break;
        case Trigger.STATE_BLOCKED:
            // do nothing cause the state will be identified by blocked jobs (so
            // we'll not overwrite paused-,error-states)
            break;
        default:
            // otherwise(pause,error,complete) just write the state
            this.fTriggerStatesByName.put(trigger.getFullName(), new Integer(state));
            this.fSerializer.saveTriggerStates(this.fTriggerStatesByName);
            break;
        }
    }

    public void pauseTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws JobPersistenceException {
        int triggerState = getTriggerState(ctxt, triggerName, groupName);
        if (triggerState != Trigger.STATE_NORMAL && triggerState != Trigger.STATE_ERROR)
            return;

        Trigger trigger = retrieveTrigger(ctxt, triggerName, groupName);
        synchronized (this.fTriggersByName) {
            setTriggerState(ctxt, trigger, Trigger.STATE_PAUSED);
            this.fOrderedTriggers.remove(this.fTriggersByName.get(trigger.getFullName()));
        }
    }

    public void pauseTriggerGroup(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            if (this.fPausedTriggerGroups.contains(groupName))
                return;

            this.fPausedTriggerGroups.add(groupName);
            this.fSerializer.savePausedTriggerGroups(this.fPausedTriggerGroups);
            String[] names = getTriggerNames(ctxt, groupName);
            for (int i = 0; i < names.length; i++) {
                pauseTrigger(ctxt, names[i], groupName);
            }
        }
    }

    public void pauseJob(SchedulingContext ctxt, String jobName, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName);
            for (int j = 0; j < triggers.length; j++) {
                pauseTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
            }
        }
    }

    public void pauseJobGroup(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            String[] jobNames = getJobNames(ctxt, groupName);
            for (int i = 0; i < jobNames.length; i++) {
                Trigger[] triggers = getTriggersForJob(ctxt, jobNames[i], groupName);
                for (int j = 0; j < triggers.length; j++) {
                    pauseTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
                }
            }
        }
    }

    public void resumeTrigger(SchedulingContext ctxt, String triggerName, String groupName)
            throws JobPersistenceException {
        int triggerState = getTriggerState(ctxt, triggerName, groupName);
        if (triggerState != Trigger.STATE_PAUSED)
            return;

        Trigger trigger = retrieveTrigger(ctxt, triggerName, groupName);
        synchronized (this.fTriggersByName) {
            if (this.fBlockedJobs.contains(trigger.getFullJobName())) {
                setTriggerState(ctxt, trigger, Trigger.STATE_BLOCKED);
            } else {
                setTriggerState(ctxt, trigger, Trigger.STATE_NORMAL);
            }

            applyMisfire(ctxt, trigger);
            if (getTriggerState(null, triggerName, groupName) == Trigger.STATE_NORMAL) {
                this.fOrderedTriggers.add(trigger);
            }
        }
    }

    private boolean applyMisfire(SchedulingContext ctxt, Trigger trigger) throws JobPersistenceException {
        long misfireTime = System.currentTimeMillis() - getMisfireThreshold();
        Date nextFireTime = trigger.getNextFireTime();
        if (nextFireTime.getTime() > misfireTime) {
            return false; // we are in time
        }

        // notify for misfire
        this.fSignaler.notifyTriggerListenersMisfired(trigger);

        // calculate next fire time
        Calendar calender = retrieveCalendar(null, trigger.getCalendarName());
        trigger.updateAfterMisfire(calender);
        if (trigger.getNextFireTime() == null) {
            setTriggerState(ctxt, trigger, Trigger.STATE_COMPLETE);
            synchronized (this.fTriggersByName) {
                this.fOrderedTriggers.remove(trigger);
            }
        } else if (nextFireTime.equals(trigger.getNextFireTime()))
            return false;

        return true;
    }

    public void resumeTriggerGroup(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            String[] names = getTriggerNames(ctxt, groupName);
            for (int i = 0; i < names.length; i++) {
                resumeTrigger(ctxt, names[i], groupName);
            }
            this.fPausedTriggerGroups.remove(groupName);
            this.fSerializer.saveTriggerStates(this.fTriggerStatesByName);
        }
    }

    public Set getPausedTriggerGroups(SchedulingContext ctxt) throws JobPersistenceException {
        HashSet set = new HashSet();
        set.addAll(this.fPausedTriggerGroups);

        return set;
    }

    public void resumeJob(SchedulingContext ctxt, String jobName, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName);
            for (int j = 0; j < triggers.length; j++) {
                resumeTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
            }
        }
    }

    public void resumeJobGroup(SchedulingContext ctxt, String groupName) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            String[] jobNames = getJobNames(ctxt, groupName);
            for (int i = 0; i < jobNames.length; i++) {
                Trigger[] triggers = getTriggersForJob(ctxt, jobNames[i], groupName);
                for (int j = 0; j < triggers.length; j++) {
                    resumeTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
                }
            }
        }
    }

    public void pauseAll(SchedulingContext ctxt) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            String[] names = getTriggerGroupNames(ctxt);
            for (int i = 0; i < names.length; i++) {
                pauseTriggerGroup(ctxt, names[i]);
            }
        }
    }

    public void resumeAll(SchedulingContext ctxt) throws JobPersistenceException {
        synchronized (this.fPausedTriggerGroups) {
            String[] names = getTriggerGroupNames(ctxt);

            for (int i = 0; i < names.length; i++) {
                resumeTriggerGroup(ctxt, names[i]);
            }
        }
    }

    public Trigger acquireNextTrigger(SchedulingContext ctxt, long noLaterThan) throws JobPersistenceException {
        synchronized (this.fTriggersByName) {
            while (!this.fOrderedTriggers.isEmpty()) {
                Trigger trigger = (Trigger) this.fOrderedTriggers.first();
                this.fOrderedTriggers.remove(trigger);

                // have one canditate
                if (trigger.getNextFireTime() != null && !applyMisfire(ctxt, trigger)) {
                    if (trigger.getNextFireTime().getTime() > noLaterThan) {
                        this.fOrderedTriggers.add(trigger);
                        return null;
                    }
                    trigger.setFireInstanceId(getFiredTriggerRecordId());
                    this.fSerializer.saveTriggers(this.fTriggersByName);
                    return (Trigger) trigger.clone();
                }

                if (trigger.getNextFireTime() != null) {
                    this.fOrderedTriggers.add(trigger);
                }
            }
        }
        return null;
    }

    public void releaseAcquiredTrigger(SchedulingContext ctxt, Trigger trigger) throws JobPersistenceException {
        trigger = (Trigger) this.fTriggersByName.get(trigger.getFullName());
        if (trigger != null) {
            this.fOrderedTriggers.add(trigger);
        }
    }

    public TriggerFiredBundle triggerFired(SchedulingContext ctxt, Trigger trigger) throws JobPersistenceException {
        synchronized (this.fTriggersByName) {
            Trigger storeTrigger = (Trigger) this.fTriggersByName.get(trigger.getFullName());
            int triggerState = getTriggerState(ctxt, trigger.getName(), trigger.getGroup());
            if (triggerState != Trigger.STATE_NORMAL)
                return null;

            Calendar calender = retrieveCalendar(ctxt, storeTrigger.getCalendarName());
            Date prevFireTime = trigger.getPreviousFireTime();
            // call triggered on our copy, and the scheduler's copy
            trigger.triggered(calender);
            storeTrigger.triggered(calender);

            TriggerFiredBundle fireBundle = new TriggerFiredBundle(
                    retrieveJob(ctxt, trigger.getJobName(), trigger.getJobGroup()), trigger, calender, false,
                    new Date(), trigger.getPreviousFireTime(), prevFireTime, trigger.getNextFireTime());
            JobDetail job = fireBundle.getJobDetail();

            if (job.isStateful()) {
                Trigger[] triggers = getTriggersForJob(ctxt, job.getName(), job.getGroup());
                for (int i = 0; i < triggers.length; i++) {
                    setTriggerState(ctxt, triggers[i], Trigger.STATE_BLOCKED);
                    this.fOrderedTriggers.remove(triggers[i]);
                }
                this.fBlockedJobs.add(job.getFullName());
            } else if (storeTrigger.getNextFireTime() != null) {
                this.fOrderedTriggers.add(storeTrigger);
            }

            return fireBundle;
        }
    }

    public void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger, JobDetail jobDetail,
            int triggerInstCode) throws JobPersistenceException {
        synchronized (this.fTriggersByName) {
            this.fBlockedJobs.remove(jobDetail.getFullName());
            JobDetail oldJobDetail = retrieveJob(ctxt, jobDetail.getName(), jobDetail.getGroup());
            Trigger oldTrigger = (Trigger) this.fTriggersByName.get(trigger.getFullName());

            updateJobDataMap(jobDetail, oldJobDetail);
            updateTriggerState(ctxt, trigger, oldTrigger, triggerInstCode);
        }
    }

    private void updateJobDataMap(JobDetail newJob, JobDetail oldJob) throws JobPersistenceException {
        if (oldJob == null || !newJob.isStateful())
            return;

        JobDataMap newData = newJob.getJobDataMap();
        if (newData != null)
            newData.clearDirtyFlag();
        oldJob.setJobDataMap(newData);
        this.fSerializer.saveJobs(this.fJobsByName);
    }

    private void updateTriggerState(SchedulingContext ctxt, Trigger newTrigger, Trigger oldTrigger,
            int triggerInstruction) throws JobPersistenceException {
        if (oldTrigger == null)
            return;

        switch (triggerInstruction) {
        case Trigger.INSTRUCTION_DELETE_TRIGGER:
            if (newTrigger.getNextFireTime() != null)
                removeTrigger(ctxt, newTrigger.getName(), newTrigger.getGroup());
            else if (oldTrigger.getNextFireTime() == null)
                removeTrigger(ctxt, newTrigger.getName(), newTrigger.getGroup());
            break;
        case Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE:
            setTriggerState(ctxt, oldTrigger, Trigger.STATE_COMPLETE);
            this.fOrderedTriggers.remove(oldTrigger);
            break;
        case Trigger.INSTRUCTION_SET_TRIGGER_ERROR:
            setTriggerState(ctxt, oldTrigger, Trigger.STATE_ERROR);
            log.info("Trigger " + oldTrigger.getFullName() + " set to ERROR state.");
            break;
        case Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE:
            Trigger[] triggers1 = getTriggersForJob(ctxt, oldTrigger.getJobName(), oldTrigger.getJobGroup());
            for (int i = 0; i < triggers1.length; i++) {
                setTriggerState(ctxt, triggers1[i], Trigger.STATE_COMPLETE);
            }
            break;
        case Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR:
            Trigger[] triggers2 = getTriggersForJob(ctxt, oldTrigger.getJobName(), oldTrigger.getJobGroup());
            for (int i = 0; i < triggers2.length; i++) {
                setTriggerState(ctxt, triggers2[i], Trigger.STATE_ERROR);
            }
            log.info("All triggers of Job " + oldTrigger.getFullJobName() + " set to ERROR state.");
            break;
        default:
            log.debug("unknown trigger instruction: " + triggerInstruction);
            break;
        }
    }

    // -------------------------non-interface---------------------

    /**
     * Returns the number of milliseconds in those the job would be executed delayed before misfired.
     * @return The number of milliseconds in those the job would be executed delayed before misfired.
     */
    public long getMisfireThreshold() {

        return this.fMisfireThreshold;
    }

    /**
     * Sets the number of milliseconds in those the job would be executed delayed before misfired.
     * @param misfireThreshold
     *            The number of milliseconds in those the job would be executed delayed before misfired.
     */
    public void setMisfireThreshold(long misfireThreshold) {
        this.fMisfireThreshold = misfireThreshold;
    }

    /**
     * Returns the store file path.
     * @return The store file path.
     */
    public String getStoreFilePath() {
        return this.fStoreDirectory;
    }

    /**
     * Sets the store file path.
     * @param storeFilePath
     *            The storeFilePath to set.
     */
    public void setStoreFilePath(String storeFilePath) {
        this.fStoreDirectory = storeFilePath;
    }

    /**
     * Removes all persistent data.
     */
    public void clear() {
        this.fJobsByName.clear();
        this.fTriggersByName.clear();
        this.fCalendarsByName.clear();
        this.fTriggerStatesByName.clear();
        this.fPausedTriggerGroups.clear();

        this.fJobsByGroup.clear();
        this.fTriggersByGroup.clear();
        this.fBlockedJobs.clear();
        this.fOrderedTriggers.clear();

        this.fSerializer.clear();
    }

    private static long ftrCtr = System.currentTimeMillis();

    private synchronized String getFiredTriggerRecordId() {
        return String.valueOf(ftrCtr++);
    }

    // -------------------------persistence-related---------------------

    private void initPeristentFields() throws JobPersistenceException {

        this.fJobsByName = (HashMap) this.fSerializer.loadJobs();
        this.fTriggersByName = (HashMap) this.fSerializer.loadTriggers();
        this.fCalendarsByName = (HashMap) this.fSerializer.loadCalendars();
        this.fTriggerStatesByName = (HashMap) this.fSerializer.loadTriggerStates();
        this.fPausedTriggerGroups = (HashSet) this.fSerializer.loadPausedTriggerGroups();

        if (this.fJobsByName == null)
            this.fJobsByName = new HashMap();
        if (this.fTriggersByName == null)
            this.fTriggersByName = new HashMap();
        if (this.fCalendarsByName == null)
            this.fCalendarsByName = new HashMap();
        if (this.fTriggerStatesByName == null)
            this.fTriggerStatesByName = new HashMap();
        if (this.fPausedTriggerGroups == null)
            this.fPausedTriggerGroups = new HashSet();
    }

    private void fillTransientFields() {
        for (Iterator iter = this.fJobsByName.values().iterator(); iter.hasNext();) {
            JobDetail job = (JobDetail) iter.next();
            addJobGroup(job);
        }
        for (Iterator iter = this.fTriggersByName.values().iterator(); iter.hasNext();) {
            Trigger trigger = (Trigger) iter.next();
            addTriggerGroup(trigger);
        }
        this.fOrderedTriggers.addAll(this.fTriggersByName.values());
    }
}