Package com.threepillar.labs.quartz.simpledb

Source Code of com.threepillar.labs.quartz.simpledb.SimpleDbJobStore

package com.threepillar.labs.quartz.simpledb;

/*
* Copyright 2001-2009 3Pillar Global Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*  
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.util.ISO8601DateFormat;
import org.quartz.Calendar;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobPersistenceException;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
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;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpledb.AmazonSimpleDBClient;
import com.amazonaws.services.simpledb.model.Attribute;
import com.amazonaws.services.simpledb.model.BatchPutAttributesRequest;
import com.amazonaws.services.simpledb.model.CreateDomainRequest;
import com.amazonaws.services.simpledb.model.DeleteAttributesRequest;
import com.amazonaws.services.simpledb.model.DeleteDomainRequest;
import com.amazonaws.services.simpledb.model.GetAttributesRequest;
import com.amazonaws.services.simpledb.model.GetAttributesResult;
import com.amazonaws.services.simpledb.model.Item;
import com.amazonaws.services.simpledb.model.PutAttributesRequest;
import com.amazonaws.services.simpledb.model.ReplaceableAttribute;
import com.amazonaws.services.simpledb.model.ReplaceableItem;
import com.amazonaws.services.simpledb.model.SelectRequest;
import com.amazonaws.services.simpledb.model.SelectResult;

/**
* <p>
* This class implements a <code>{@link org.quartz.spi.JobStore}</code> that
* utilizes AWS SimpleDB as its storage device.
* </p>
*
* <p>
* Important: Currently it does not handle durable jobs and misfiring
* </p>
*
* @author Abhinav Maheshwari
*/
public class SimpleDbJobStore implements JobStore {

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Attribute names in Job Domain.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */
  private static final String JOB_CLASS = "class";
  private static final String JOB_JOBCLASS = "jobClass";
  private static final String JOB_GROUP = "group";
  private static final String JOB_NAME = "name";
  private static final String JOB_DATA_MAP = "jobDataMap";

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Attribute names in Trigger domain
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */
  private static final String TRIGGER_CLASS = "class";
  private static final String TRIGGER_GROUP = "group";
  private static final String TRIGGER_NAME = "name";
  private static final String TRIGGER_PRIORITY = "priority";
  private static final String TRIGGER_START_TIME = "startTime";
  private static final String TRIGGER_NEXT_FIRE_TIME = "nextFireTime";
  private static final String TRIGGER_END_TIME = "endTime";
  private static final String TRIGGER_JOB_NAME = "jobName";
  private static final String TRIGGER_JOB_GROUP = "jobGroup";
  private static final String TRIGGER_CALENDAR_NAME = "calendarName";
  private static final String TRIGGER_STATE = "state";
  private static final String TRIGGER_JSON = "json";
  private static final String TRIGGER_JSON_LENGTH = "json_length";

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Domain names for SimpleDB.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */
  private static final String JOB_DOMAIN = "Jobs";
  private static final String TRIGGER_DOMAIN = "Triggers";
  private static final int MAX_ATTR_LENGTH = 1024;

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Configuration parameters.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */

  private String awsAccessKey;
  private String awsSecretKey;
  private String prefix;
  private boolean recreate;

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Data members.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */
  private AmazonSimpleDBClient amazonSimpleDb;
  private String jobDomain;
  private String triggerDomain;
  private final ObjectMapper mapper;
  private QueryBuilder query;
  private final ISO8601DateFormat dateFormat = new ISO8601DateFormat();

  protected final Object lock = new Object();
  protected long misfireThreshold = 5000l;
  protected SchedulerSignaler signaler;
  private final Log log = LogFactory.getLog(getClass());

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Constructors.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */

  /**
   * <p>
   * Create a new <code>RAMJobStore</code>.
   * </p>
   */
  public SimpleDbJobStore() {
    this.mapper = new ObjectMapper();
    this.mapper.getDeserializationConfig().addMixInAnnotations(
        Trigger.class, FixCronTriggerMixIn.class);
    this.mapper.getDeserializationConfig().addMixInAnnotations(
        JobDetail.class, FixJobDetailMixIn.class);
    this.mapper.getDeserializationConfig().addMixInAnnotations(
        CronTrigger.class, FixTriggerMixIn.class);
    this.mapper.getDeserializationConfig().addMixInAnnotations(
        SimpleTrigger.class, FixTriggerMixIn.class);
  }

  /*
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *
   * Interface.
   *
   * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   */

  protected Log getLog() {
    return log;
  }

  public void setAwsAccessKey(String accessKey) {
    this.awsAccessKey = accessKey;
  }

  public void setAwsSecretKey(String secretKey) {
    this.awsSecretKey = secretKey;
  }

  public void setPrefix(String prefix) {
    this.prefix = prefix;
  }

  public void setRecreate(boolean recreate) {
    this.recreate = recreate;
  }

  /**
   * <p>
   * Called by the QuartzScheduler before the <code>JobStore</code> is used,
   * in order to give the it a chance to initialize.
   * </p>
   */
  @Override
  public void initialize(ClassLoadHelper loadHelper,
      SchedulerSignaler signaler) {
    log.debug("Initializing SimpleDbJobStore");
    this.jobDomain = String.format("%s.%s", prefix,
        SimpleDbJobStore.JOB_DOMAIN);
    this.triggerDomain = String.format("%s.%s", prefix,
        SimpleDbJobStore.TRIGGER_DOMAIN);
    this.query = new QueryBuilder(this.jobDomain, this.triggerDomain);
    this.amazonSimpleDb = new AmazonSimpleDBClient(new BasicAWSCredentials(
        awsAccessKey, awsSecretKey));
    this.signaler = signaler;

    boolean foundJobs = false, foundTriggers = false;
    for (String name : amazonSimpleDb.listDomains().getDomainNames()) {
      if (name.equals(jobDomain)) {
        if (recreate) {
          amazonSimpleDb.deleteDomain(new DeleteDomainRequest(
              jobDomain));
        } else {
          foundJobs = true;
        }
      }
      if (name.equals(triggerDomain)) {
        if (recreate) {
          amazonSimpleDb.deleteDomain(new DeleteDomainRequest(
              triggerDomain));
        } else {
          foundTriggers = true;
        }
      }
      if (foundJobs && foundTriggers) {
        break;
      }
    }
    if (!foundJobs) {
      amazonSimpleDb.createDomain(new CreateDomainRequest(jobDomain));
    }
    if (!foundTriggers) {
      amazonSimpleDb.createDomain(new CreateDomainRequest(triggerDomain));
    }
    log.info("SimpleDbJobStore initialized.");
  }

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

  public long getMisfireThreshold() {
    return misfireThreshold;
  }

  /**
   * The number of milliseconds by which a trigger must have missed its
   * next-fire-time, in order for it to be considered "misfired" and thus have
   * its misfire instruction applied.
   *
   * @param misfireThreshold
   */
  public void setMisfireThreshold(long misfireThreshold) {
    if (misfireThreshold < 1) {
      throw new IllegalArgumentException(
          "Misfirethreashold must be larger than 0");
    }
    this.misfireThreshold = misfireThreshold;
  }

  /**
   * <p>
   * Called by the QuartzScheduler to inform the <code>JobStore</code> that it
   * should free up all of it's resources because the scheduler is shutting
   * down.
   * </p>
   */
  @Override
  public void shutdown() {
  }

  @Override
  public boolean supportsPersistence() {
    return true;
  }

  /**
   * <p>
   * Store the given <code>{@link org.quartz.JobDetail}</code> and
   * <code>{@link org.quartz.Trigger}</code>.
   * </p>
   *
   * @param newJob
   *            The <code>JobDetail</code> to be stored.
   * @param newTrigger
   *            The <code>Trigger</code> to be stored.
   * @throws ObjectAlreadyExistsException
   *             if a <code>Job</code> with the same name/group already
   *             exists.
   */
  @Override
  public void storeJobAndTrigger(SchedulingContext ctxt, JobDetail newJob,
      Trigger newTrigger) throws JobPersistenceException {
    storeJob(ctxt, newJob, false);
    storeTrigger(ctxt, newTrigger, false);
  }

  /**
   * <p>
   * Store the given <code>{@link org.quartz.Job}</code>.
   * </p>
   *
   * @param newJob
   *            The <code>Job</code> to be stored.
   * @param replaceExisting
   *            If <code>true</code>, any <code>Job</code> existing in the
   *            <code>JobStore</code> with the same name & group should be
   *            over-written.
   * @throws ObjectAlreadyExistsException
   *             if a <code>Job</code> with the same name/group already
   *             exists, and replaceExisting is set to false.
   */
  @Override
  public void storeJob(SchedulingContext ctxt, JobDetail newJob,
      boolean replaceExisting) throws ObjectAlreadyExistsException {

    log.debug("Storing Job: " + newJob.getFullName());
    ReplaceableItem item = null;
    try {
      List<ReplaceableAttribute> attributes = new ArrayList<ReplaceableAttribute>();
      attributes.add(new ReplaceableAttribute(JOB_NAME, newJob.getName(),
          true));
      attributes.add(new ReplaceableAttribute(JOB_GROUP, newJob
          .getGroup(), true));
      attributes.add(new ReplaceableAttribute(JOB_JOBCLASS, newJob
          .getJobClass().getName(), true));
      attributes.add(new ReplaceableAttribute(JOB_CLASS, newJob
          .getClass().getName(), true));
      if (newJob.getJobDataMap() != null) {
        attributes.add(new ReplaceableAttribute(JOB_DATA_MAP, mapper
            .writeValueAsString(newJob.getJobDataMap()), true));
      }
      item = new ReplaceableItem(JobWrapper.getJobNameKey(newJob),
          attributes);
      amazonSimpleDb.batchPutAttributes(new BatchPutAttributesRequest(
          jobDomain, Collections.singletonList(item)));
    } catch (Exception e) {
      log.error("Could not store Job: " + newJob.getFullName(), e);
    }

  }

  /**
   * <p>
   * Remove (delete) the <code>{@link org.quartz.Job}</code> with the given
   * name, and any <code>{@link org.quartz.Trigger}</code> s that reference
   * it.
   * </p>
   *
   * @param jobName
   *            The name of the <code>Job</code> to be removed.
   * @param groupName
   *            The group name of the <code>Job</code> to be removed.
   * @return <code>true</code> if a <code>Job</code> with the given name &
   *         group was found and removed from the store.
   */
  @Override
  public boolean removeJob(SchedulingContext ctxt, String jobName,
      String groupName) {
    log.debug("Removing Job: " + groupName + "." + jobName);
    try {
      String key = JobWrapper.getJobNameKey(jobName, groupName);
      amazonSimpleDb.deleteAttributes(new DeleteAttributesRequest(
          jobDomain, key));
      return true;
    } catch (Exception e) {
      log.error("Could not remove Job: " + groupName + "." + jobName, e);
      return false;
    }
  }

  /**
   * <p>
   * Store the given <code>{@link org.quartz.Trigger}</code>.
   * </p>
   *
   * @param newTrigger
   *            The <code>Trigger</code> to be stored.
   * @param replaceExisting
   *            If <code>true</code>, any <code>Trigger</code> existing in the
   *            <code>JobStore</code> with the same name & group should be
   *            over-written.
   * @throws ObjectAlreadyExistsException
   *             if a <code>Trigger</code> with the same name/group already
   *             exists, and replaceExisting is set to false.
   *
   * @see #pauseTriggerGroup(SchedulingContext, String)
   */
  @Override
  public void storeTrigger(SchedulingContext ctxt, Trigger newTrigger,
      boolean replaceExisting) throws JobPersistenceException {

    log.debug("Storing Trigger: " + newTrigger.getFullName());
    ReplaceableItem item = null;
    try {

      List<ReplaceableAttribute> attributes = new ArrayList<ReplaceableAttribute>();
      attributes.add(new ReplaceableAttribute(TRIGGER_CLASS, newTrigger
          .getClass().getName(), true));
      attributes.add(new ReplaceableAttribute(TRIGGER_NAME, newTrigger
          .getName(), true));
      if (newTrigger.getCalendarName() != null) {
        attributes.add(new ReplaceableAttribute(TRIGGER_CALENDAR_NAME,
            newTrigger.getCalendarName(), true));
      }
      attributes.add(new ReplaceableAttribute(TRIGGER_GROUP, newTrigger
          .getGroup(), true));
      attributes.add(new ReplaceableAttribute(TRIGGER_JOB_GROUP,
          newTrigger.getJobGroup(), true));
      attributes.add(new ReplaceableAttribute(TRIGGER_JOB_NAME,
          newTrigger.getJobName(), true));
      attributes.add(new ReplaceableAttribute(TRIGGER_PRIORITY, String
          .valueOf(newTrigger.getPriority()), true));
      if (newTrigger.getEndTime() != null) {
        attributes.add(new ReplaceableAttribute(TRIGGER_END_TIME,
            dateFormat.format(newTrigger.getEndTime()), true));
      }
      if (newTrigger.getStartTime() != null) {
        attributes.add(new ReplaceableAttribute(TRIGGER_START_TIME,
            dateFormat.format(newTrigger.getStartTime()), true));
      }
      if (newTrigger.getNextFireTime() != null) {
        attributes.add(new ReplaceableAttribute(TRIGGER_NEXT_FIRE_TIME,
            dateFormat.format(newTrigger.getNextFireTime()), true));
      }
      item = new ReplaceableItem(
          TriggerWrapper.getTriggerNameKey(newTrigger), attributes);
      String json = mapper.writeValueAsString(newTrigger);
      attributes.add(new ReplaceableAttribute(TRIGGER_JSON_LENGTH, String
          .valueOf(json.length()), true));

      // Store the JSON representation in multiple attributes since max
      // length is 1024
      for (int i = 0; json.length() > i * MAX_ATTR_LENGTH; i++) {
        int end = Math.min((i + 1) * MAX_ATTR_LENGTH, json.length());
        attributes.add(new ReplaceableAttribute(TRIGGER_JSON
            + String.valueOf(i), json.substring(
            i * MAX_ATTR_LENGTH, end), true));
      }
      amazonSimpleDb.batchPutAttributes(new BatchPutAttributesRequest(
          triggerDomain, Collections.singletonList(item)));
    } catch (Exception e) {
      log.error("Could not store Trigger: " + newTrigger.getFullName(), e);
    }

  }

  /**
   * <p>
   * Remove (delete) the <code>{@link org.quartz.Trigger}</code> with the
   * given name.
   * </p>
   *
   * @param triggerName
   *            The name of the <code>Trigger</code> to be removed.
   * @param groupName
   *            The group name of the <code>Trigger</code> to be removed.
   * @return <code>true</code> if a <code>Trigger</code> with the given name &
   *         group was found and removed from the store.
   */
  @Override
  public boolean removeTrigger(SchedulingContext ctxt, String triggerName,
      String groupName) {
    return removeTrigger(ctxt, triggerName, groupName, true);
  }

  private boolean removeTrigger(SchedulingContext ctxt, String triggerName,
      String groupName, boolean removeOrphanedJob) {
    log.debug("Removing Trigger: " + groupName + "." + triggerName);
    try {
      String key = TriggerWrapper.getTriggerNameKey(triggerName,
          groupName);
      amazonSimpleDb.deleteAttributes(new DeleteAttributesRequest(
          triggerDomain, key));
      return true;
    } catch (Exception e) {
      log.error("Could not remove Trigger: " + groupName + "."
          + triggerName, e);
      return false;
    }
  }

  /**
   * @see org.quartz.spi.JobStore#replaceTrigger(org.quartz.core.SchedulingContext,
   *      java.lang.String, java.lang.String, org.quartz.Trigger)
   */
  @Override
  public boolean replaceTrigger(SchedulingContext ctxt, String triggerName,
      String groupName, Trigger newTrigger)
      throws JobPersistenceException {
    log.debug("Replacing Trigger: " + triggerName + "." + groupName
        + " with " + newTrigger.getFullName());
    Trigger found = retrieveTrigger(ctxt, triggerName, groupName);

    if (found != null) {
      if (!found.getJobName().equals(newTrigger.getJobName())
          || !found.getJobGroup().equals(newTrigger.getJobGroup())) {
        throw new JobPersistenceException(
            "New trigger is not related to the same job as the old trigger.");
      }
      try {
        removeTrigger(ctxt, triggerName, groupName);
        storeTrigger(ctxt, newTrigger, false);
      } catch (JobPersistenceException jpe) {
        storeTrigger(ctxt, found, false); // put previous trigger
                          // back...
        throw jpe;
      }
    }

    return (found != null);
  }

  /**
   * <p>
   * Retrieve the <code>{@link org.quartz.JobDetail}</code> for the given
   * <code>{@link org.quartz.Job}</code>.
   * </p>
   *
   * @param jobName
   *            The name of the <code>Job</code> to be retrieved.
   * @param groupName
   *            The group name of the <code>Job</code> to be retrieved.
   * @return The desired <code>Job</code>, or null if there is no match.
   */
  @Override
  public JobDetail retrieveJob(SchedulingContext ctxt, String jobName,
      String groupName) {
    log.debug("Retrieving Job: " + groupName + "." + jobName);
    String key = JobWrapper.getJobNameKey(jobName, groupName);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(jobDomain, key)
            .withConsistentRead(new Boolean(true)));
    JobDetail job = null;
    try {
      job = jobDetailFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Could not retrieve Job: " + groupName + "." + jobName, e);
    }
    return job;
  }

  private JobDetail jobDetailFromAttributes(List<Attribute> attributes)
      throws Exception {
    if (attributes == null || attributes.size() == 0) {
      throw new Exception("No attributes");
    }
    Map<String, String> map = new HashMap<String, String>();
    for (Attribute attr : attributes) {
      map.put(attr.getName(), attr.getValue());
    }
    Class<? extends JobDetail> clz = Class.forName(map.get(JOB_CLASS))
        .asSubclass(JobDetail.class);
    JobDetail jobDetail = clz.newInstance();
    jobDetail.setJobClass(Class.forName(map.get(JOB_JOBCLASS)));
    jobDetail.setName(map.get(JOB_NAME));
    jobDetail.setGroup(map.get(JOB_GROUP));
    jobDetail.setJobDataMap(mapper.readValue(map.get(JOB_DATA_MAP),
        JobDataMap.class));
    return jobDetail;
  }

  /**
   * <p>
   * Retrieve the given <code>{@link org.quartz.Trigger}</code>.
   * </p>
   *
   * @param triggerName
   *            The name of the <code>Trigger</code> to be retrieved.
   * @param groupName
   *            The group name of the <code>Trigger</code> to be retrieved.
   * @return The desired <code>Trigger</code>, or null if there is no match.
   */
  @Override
  public Trigger retrieveTrigger(SchedulingContext ctxt, String triggerName,
      String groupName) {
    log.debug("Retrieving Trigger: " + triggerName + "." + groupName);
    String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;
    try {
      tw = triggerFromAttributes(result.getAttributes());
      return tw.trigger;
    } catch (Exception e) {
      log.error("Could not retrieve Trigger: " + triggerName + "."
          + groupName, e);
    }
    return null;
  }

  private TriggerWrapper triggerFromAttributes(List<Attribute> attributes)
      throws Exception {
    if (attributes == null || attributes.size() == 0) {
      throw new Exception("No attributes");
    }

    Map<String, String> map = new HashMap<String, String>();
    for (Attribute attr : attributes) {
      map.put(attr.getName(), attr.getValue());
    }

    Class<? extends Trigger> clz = Class.forName(map.get(TRIGGER_CLASS))
        .asSubclass(Trigger.class);

    int len = Integer.parseInt(map.get(TRIGGER_JSON_LENGTH));
    int n = (len - 1) / MAX_ATTR_LENGTH + 1;
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < n; i++) {
      buf.append(map.get(TRIGGER_JSON + String.valueOf(i)));
    }

    Trigger trigger = mapper.readValue(buf.toString(), clz);
    trigger.setName(map.get(TRIGGER_NAME));
    trigger.setGroup(map.get(TRIGGER_GROUP));
    if (map.get(TRIGGER_CALENDAR_NAME) != null) {
      trigger.setCalendarName(map.get(TRIGGER_CALENDAR_NAME));
    }
    trigger.setJobName(map.get(TRIGGER_JOB_NAME));
    trigger.setJobGroup(map.get(TRIGGER_JOB_GROUP));
    trigger.setStartTime(dateFormat.parse(map.get(TRIGGER_START_TIME)));
    if (map.get(TRIGGER_END_TIME) != null) {
      trigger.setEndTime(dateFormat.parse(map.get(TRIGGER_END_TIME)));
    }
    trigger.setPriority(Integer.parseInt(map.get(TRIGGER_PRIORITY)));

    TriggerWrapper wrapper = new TriggerWrapper(trigger);
    if (map.get(TRIGGER_STATE) != null) {
      wrapper.state = Integer.parseInt(map.get(TRIGGER_STATE));
    }
    return wrapper;
  }

  private void updateState(TriggerWrapper tw) {
    log.debug("Updating state of Trigger: " + tw.trigger.getFullName());
    String key = TriggerWrapper.getTriggerNameKey(tw.trigger);
    ReplaceableAttribute attr = new ReplaceableAttribute(TRIGGER_STATE,
        String.valueOf(tw.state), true);
    amazonSimpleDb.putAttributes(new PutAttributesRequest(triggerDomain,
        key, Collections.singletonList(attr)));
  }

  /**
   * <p>
   * Get the current state of the identified <code>{@link Trigger}</code>.
   * </p>
   *
   * @see Trigger#STATE_NORMAL
   * @see Trigger#STATE_PAUSED
   * @see Trigger#STATE_COMPLETE
   * @see Trigger#STATE_ERROR
   * @see Trigger#STATE_BLOCKED
   * @see Trigger#STATE_NONE
   */
  @Override
  public int getTriggerState(SchedulingContext ctxt, String triggerName,
      String groupName) throws JobPersistenceException {
    log.debug("Finding state of Trigger: " + triggerName + "." + groupName);
    String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName);

    TriggerWrapper tw = null;
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    try {
      tw = triggerFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Error finding state of Trigger: " + triggerName + "."
          + groupName, e);
    }

    if (tw == null) {
      return Trigger.STATE_NONE;
    }

    switch (tw.state) {
    case TriggerWrapper.STATE_COMPLETE:
      return Trigger.STATE_COMPLETE;
    case TriggerWrapper.STATE_PAUSED:
      return Trigger.STATE_PAUSED;
    case TriggerWrapper.STATE_PAUSED_BLOCKED:
      return Trigger.STATE_PAUSED;
    case TriggerWrapper.STATE_BLOCKED:
      return Trigger.STATE_BLOCKED;
    case TriggerWrapper.STATE_ERROR:
      return Trigger.STATE_ERROR;
    default:
      return Trigger.STATE_NORMAL;
    }

  }

  /**
   * <p>
   * Store the given <code>{@link org.quartz.Calendar}</code>.
   * </p>
   *
   * @param calendar
   *            The <code>Calendar</code> to be stored.
   * @param replaceExisting
   *            If <code>true</code>, any <code>Calendar</code> existing in
   *            the <code>JobStore</code> with the same name & group should be
   *            over-written.
   * @param updateTriggers
   *            If <code>true</code>, any <code>Trigger</code>s existing in
   *            the <code>JobStore</code> that reference an existing Calendar
   *            with the same name with have their next fire time re-computed
   *            with the new <code>Calendar</code>.
   * @throws ObjectAlreadyExistsException
   *             if a <code>Calendar</code> with the same name already exists,
   *             and replaceExisting is set to false.
   */
  @Override
  public void storeCalendar(SchedulingContext ctxt, String name,
      Calendar calendar, boolean replaceExisting, boolean updateTriggers)
      throws ObjectAlreadyExistsException {

  }

  /**
   * <p>
   * Remove (delete) the <code>{@link org.quartz.Calendar}</code> with the
   * given name.
   * </p>
   *
   * <p>
   * If removal of the <code>Calendar</code> would result in
   * <code>Trigger</code>s pointing to non-existent calendars, then a
   * <code>JobPersistenceException</code> will be thrown.
   * </p>
   * *
   *
   * @param calName
   *            The name of the <code>Calendar</code> to be removed.
   * @return <code>true</code> if a <code>Calendar</code> with the given name
   *         was found and removed from the store.
   */
  @Override
  public boolean removeCalendar(SchedulingContext ctxt, String calName)
      throws JobPersistenceException {

    return true;
  }

  /**
   * <p>
   * Retrieve the given <code>{@link org.quartz.Trigger}</code>.
   * </p>
   *
   * @param calName
   *            The name of the <code>Calendar</code> to be retrieved.
   * @return The desired <code>Calendar</code>, or null if there is no match.
   */
  @Override
  public Calendar retrieveCalendar(SchedulingContext ctxt, String calName) {
    return null;
  }

  /**
   * <p>
   * Get the number of <code>{@link org.quartz.JobDetail}</code> s that are
   * stored in the <code>JobsStore</code>.
   * </p>
   */
  @Override
  public int getNumberOfJobs(SchedulingContext ctxt) {
    log.debug("Finding number of jobs");
    try {
      SelectResult result = amazonSimpleDb.select(new SelectRequest(query
          .countJobs()));
      Item item = result.getItems().get(0);
      return Integer.parseInt(item.getAttributes().get(0).getValue());
    } catch (Exception e) {
      log.error("Could not find number of jobs", e);
      return -1;
    }
  }

  /**
   * <p>
   * Get the number of <code>{@link org.quartz.Trigger}</code> s that are
   * stored in the <code>JobsStore</code>.
   * </p>
   */
  @Override
  public int getNumberOfTriggers(SchedulingContext ctxt) {
    log.debug("Finding number of triggers");
    try {
      SelectResult result = amazonSimpleDb.select(new SelectRequest(query
          .countTriggers()));
      Item item = result.getItems().get(0);
      return Integer.parseInt(item.getAttributes().get(0).getValue());
    } catch (Exception e) {
      log.error("Could not find number of triggers", e);
      return -1;
    }
  }

  /**
   * <p>
   * Get the number of <code>{@link org.quartz.Calendar}</code> s that are
   * stored in the <code>JobsStore</code>.
   * </p>
   */
  @Override
  public int getNumberOfCalendars(SchedulingContext ctxt) {
    return 0;
  }

  /**
   * <p>
   * Get the names of all of the <code>{@link org.quartz.Job}</code> s that
   * have the given group name.
   * </p>
   */
  @Override
  public String[] getJobNames(SchedulingContext ctxt, String groupName) {
    log.debug("Getting names of jobs");
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .jobNamesInGroup(groupName)));
    List<Item> jobs = result.getItems();
    String[] outList = new String[jobs.size()];
    int i = 0;
    for (Item item : jobs) {
      outList[i++] = item.getAttributes().get(0).getValue();
    }
    return outList;
  }

  /**
   * <p>
   * Get the names of all of the <code>{@link org.quartz.Calendar}</code> s in
   * the <code>JobStore</code>.
   * </p>
   *
   * <p>
   * If there are no Calendars in the given group name, the result should be a
   * zero-length array (not <code>null</code>).
   * </p>
   */
  @Override
  public String[] getCalendarNames(SchedulingContext ctxt) {
    return new String[0];
  }

  /**
   * <p>
   * Get the names of all of the <code>{@link org.quartz.Trigger}</code> s
   * that have the given group name.
   * </p>
   */
  @Override
  public String[] getTriggerNames(SchedulingContext ctxt, String groupName) {
    log.debug("Getting names of triggers");
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .triggerNamesInGroup(groupName)));
    List<Item> jobs = result.getItems();

    String[] outList = new String[jobs.size()];
    int i = 0;
    for (Item item : jobs) {
      outList[i++] = item.getAttributes().get(0).getValue();
    }
    return outList;
  }

  /**
   * <p>
   * Get the names of all of the <code>{@link org.quartz.Job}</code> groups.
   * </p>
   */
  @Override
  public String[] getJobGroupNames(SchedulingContext ctxt) {
    log.debug("Getting job group names");
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .jobGroups()));
    List<Item> jobs = result.getItems();
    Set<String> groups = new HashSet<String>();
    for (Item item : jobs) {
      groups.add(item.getAttributes().get(0).getValue());
    }
    String[] outList = new String[groups.size()];
    return groups.toArray(outList);

  }

  /**
   * <p>
   * Get the names of all of the <code>{@link org.quartz.Trigger}</code>
   * groups.
   * </p>
   */
  @Override
  public String[] getTriggerGroupNames(SchedulingContext ctxt) {
    log.debug("Getting trigger group names");
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .triggerGroups()));
    List<Item> jobs = result.getItems();
    Set<String> groups = new HashSet<String>();
    for (Item item : jobs) {
      groups.add(item.getAttributes().get(0).getValue());
    }
    String[] outList = new String[groups.size()];
    return groups.toArray(outList);
  }

  /**
   * <p>
   * Get all of the Triggers that are associated to the given Job.
   * </p>
   *
   * <p>
   * If there are no matches, a zero-length array should be returned.
   * </p>
   */
  @Override
  public Trigger[] getTriggersForJob(SchedulingContext ctxt, String jobName,
      String groupName) {
    log.debug("Get triggers for Job: " + jobName + "." + groupName);
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .triggersForJob(jobName, groupName)));
    List<Item> items = result.getItems();
    Trigger[] triggers = new Trigger[items.size()];
    int i = 0;
    for (Item item : items) {
      try {
        TriggerWrapper tw = triggerFromAttributes(item.getAttributes());
        triggers[i++] = tw.trigger;
      } catch (Exception e) {
        log.error("Could not create trigger for Item: "
            + item.getName());
      }
    }
    return triggers;
  }

  /**
   * <p>
   * Pause the <code>{@link Trigger}</code> with the given name.
   * </p>
   *
   */
  @Override
  public void pauseTrigger(SchedulingContext ctxt, String triggerName,
      String groupName) {
    log.debug("Pausing Trigger: " + triggerName + "." + groupName);
    String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;

    try {
      tw = triggerFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Could not create trigger for Item: " + result.toString());
    }

    // does the trigger exist?
    if (tw == null || tw.trigger == null) {
      return;
    }

    // if the trigger is "complete" pausing it does not make sense...
    if (tw.state == TriggerWrapper.STATE_COMPLETE) {
      return;
    }

    if (tw.state == TriggerWrapper.STATE_BLOCKED) {
      tw.state = TriggerWrapper.STATE_PAUSED_BLOCKED;
    } else {
      tw.state = TriggerWrapper.STATE_PAUSED;
    }

    updateState(tw);

  }

  /**
   * <p>
   * Pause all of the <code>{@link Trigger}s</code> in the given group.
   * </p>
   *
   * <p>
   * 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.
   * </p>
   *
   */
  @Override
  public void pauseTriggerGroup(SchedulingContext ctxt, String groupName) {
    log.debug("Pausing all triggers of goup: " + groupName);
    String[] names = getTriggerNames(ctxt, groupName);

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

  /**
   * <p>
   * Pause the <code>{@link org.quartz.JobDetail}</code> with the given name -
   * by pausing all of its current <code>Trigger</code>s.
   * </p>
   *
   */
  @Override
  public void pauseJob(SchedulingContext ctxt, String jobName,
      String groupName) {
    log.debug("Pausing all triggers of Job: " + jobName + "." + groupName);
    Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName);
    for (int j = 0; j < triggers.length; j++) {
      pauseTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
    }
  }

  /**
   * <p>
   * Pause all of the <code>{@link org.quartz.JobDetail}s</code> in the given
   * group - by pausing all of their <code>Trigger</code>s.
   * </p>
   *
   *
   * <p>
   * 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.
   * </p>
   */
  @Override
  public void pauseJobGroup(SchedulingContext ctxt, String groupName) {
    log.debug("Pausing all jobs of group: " + groupName);
    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());
      }
    }
  }

  /**
   * <p>
   * Resume (un-pause) the <code>{@link Trigger}</code> with the given name.
   * </p>
   *
   * <p>
   * If the <code>Trigger</code> missed one or more fire-times, then the
   * <code>Trigger</code>'s misfire instruction will be applied.
   * </p>
   *
   */
  @Override
  public void resumeTrigger(SchedulingContext ctxt, String triggerName,
      String groupName) {
    log.debug("Resuming Trigger: " + triggerName + "." + groupName);
    String key = TriggerWrapper.getTriggerNameKey(triggerName, groupName);

    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;

    try {
      tw = triggerFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Could not create trigger for Item: " + result.toString());
    }

    // does the trigger exist?
    if (tw == null || tw.trigger == null) {
      return;
    }

    // if the trigger is not paused resuming it does not make sense...
    if (tw.state != TriggerWrapper.STATE_PAUSED
        && tw.state != TriggerWrapper.STATE_PAUSED_BLOCKED) {
      return;
    }
    applyMisfire(tw);

  }

  /**
   * <p>
   * Resume (un-pause) all of the <code>{@link Trigger}s</code> in the given
   * group.
   * </p>
   *
   * <p>
   * If any <code>Trigger</code> missed one or more fire-times, then the
   * <code>Trigger</code>'s misfire instruction will be applied.
   * </p>
   *
   */
  @Override
  public void resumeTriggerGroup(SchedulingContext ctxt, String groupName) {
    log.debug("Resuming all triggers of group: " + groupName);
    String[] names = getTriggerNames(ctxt, groupName);
    for (int i = 0; i < names.length; i++) {
      resumeTrigger(ctxt, names[i], groupName);
    }
  }

  /**
   * <p>
   * Resume (un-pause) the <code>{@link org.quartz.JobDetail}</code> with the
   * given name.
   * </p>
   *
   * <p>
   * If any of the <code>Job</code>'s<code>Trigger</code> s missed one or more
   * fire-times, then the <code>Trigger</code>'s misfire instruction will be
   * applied.
   * </p>
   *
   */
  @Override
  public void resumeJob(SchedulingContext ctxt, String jobName,
      String groupName) {
    log.debug("Resuming all triggers for Job: " + jobName + "." + groupName);
    Trigger[] triggers = getTriggersForJob(ctxt, jobName, groupName);
    for (int j = 0; j < triggers.length; j++) {
      resumeTrigger(ctxt, triggers[j].getName(), triggers[j].getGroup());
    }
  }

  /**
   * <p>
   * Resume (un-pause) all of the <code>{@link org.quartz.JobDetail}s</code>
   * in the given group.
   * </p>
   *
   * <p>
   * If any of the <code>Job</code> s had <code>Trigger</code> s that missed
   * one or more fire-times, then the <code>Trigger</code>'s misfire
   * instruction will be applied.
   * </p>
   *
   */
  @Override
  public void resumeJobGroup(SchedulingContext ctxt, String groupName) {
    log.debug("Resuming all jobs of group: " + groupName);
    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());
      }
    }
  }

  /**
   * <p>
   * Pause all triggers - equivalent of calling
   * <code>pauseTriggerGroup(group)</code> on every group.
   * </p>
   *
   * <p>
   * When <code>resumeAll()</code> is called (to un-pause), trigger misfire
   * instructions WILL be applied.
   * </p>
   *
   * @see #resumeAll(SchedulingContext)
   * @see #pauseTriggerGroup(SchedulingContext, String)
   */
  @Override
  public void pauseAll(SchedulingContext ctxt) {
    log.debug("Pausing all triggers");
    String[] names = getTriggerGroupNames(ctxt);
    for (int i = 0; i < names.length; i++) {
      pauseTriggerGroup(ctxt, names[i]);
    }
  }

  /**
   * <p>
   * Resume (un-pause) all triggers - equivalent of calling
   * <code>resumeTriggerGroup(group)</code> on every group.
   * </p>
   *
   * <p>
   * If any <code>Trigger</code> missed one or more fire-times, then the
   * <code>Trigger</code>'s misfire instruction will be applied.
   * </p>
   *
   * @see #pauseAll(SchedulingContext)
   */
  @Override
  public void resumeAll(SchedulingContext ctxt) {
    log.debug("Resuming all triggers");
    String[] names = getTriggerGroupNames(ctxt);
    for (int i = 0; i < names.length; i++) {
      resumeTriggerGroup(ctxt, names[i]);
    }
  }

  protected boolean applyMisfire(TriggerWrapper tw) {
    log.debug("Applying misfire on Trigger: " + tw.trigger.getFullName());

    long misfireTime = System.currentTimeMillis();
    if (getMisfireThreshold() > 0) {
      misfireTime -= getMisfireThreshold();
    }

    Date tnft = tw.trigger.getNextFireTime();
    if (tnft == null || tnft.getTime() > misfireTime) {
      return false;
    }

    Calendar cal = null;
    if (tw.trigger.getCalendarName() != null) {
      cal = retrieveCalendar(null, tw.trigger.getCalendarName());
    }

    signaler.notifyTriggerListenersMisfired((Trigger) tw.trigger.clone());

    tw.trigger.updateAfterMisfire(cal);

    if (tw.trigger.getNextFireTime() == null) {
      tw.state = TriggerWrapper.STATE_COMPLETE;
      signaler.notifySchedulerListenersFinalized(tw.trigger);
      removeTrigger(null, tw.trigger.getName(), tw.trigger.getGroup());
    } else if (tnft.equals(tw.trigger.getNextFireTime())) {
      return false;
    }

    return true;
  }

  private static AtomicLong ftrCtr = new AtomicLong(
      System.currentTimeMillis());

  protected String getFiredTriggerRecordId() {
    return String.valueOf(ftrCtr.incrementAndGet());
  }

  /**
   * <p>
   * Get a handle to the next trigger to be fired, and mark it as 'reserved'
   * by the calling scheduler.
   * </p>
   *
   * @see #releaseAcquiredTrigger(SchedulingContext, Trigger)
   */
  @Override
  public Trigger acquireNextTrigger(SchedulingContext ctxt, long noLaterThan) {
    log.info("Acquiring next trigger: "
        + query.acquireTrigger(dateFormat.format(new Date(noLaterThan))));
    SelectResult result = amazonSimpleDb.select(new SelectRequest(query
        .acquireTrigger(dateFormat.format(new Date(noLaterThan)))));
    List<Item> items = result.getItems();

    if (items.size() == 1) {
      try {
        TriggerWrapper tw = triggerFromAttributes(items.get(0)
            .getAttributes());
        log.debug("Acquired next Trigger: " + tw.trigger.getFullName());
        if (tw.trigger.getNextFireTime() != null) {
          tw.state = TriggerWrapper.STATE_ACQUIRED;
          updateState(tw);
          return tw.trigger;
        } else {
          removeTrigger(ctxt, tw.trigger.getName(),
              tw.trigger.getGroup());
        }
      } catch (Exception e) {
        log.error("Could not acquire trigger", e);
      }
    }
    return null;
  }

  /**
   * <p>
   * Inform the <code>JobStore</code> that the scheduler no longer plans to
   * fire the given <code>Trigger</code>, that it had previously acquired
   * (reserved).
   * </p>
   */
  @Override
  public void releaseAcquiredTrigger(SchedulingContext ctxt, Trigger trigger) {
    log.debug("Releasing Trigger: " + trigger.getFullName());
    String key = TriggerWrapper.getTriggerNameKey(trigger);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;
    try {
      tw = triggerFromAttributes(result.getAttributes());
      if (tw.state == TriggerWrapper.STATE_ACQUIRED) {
        tw.state = TriggerWrapper.STATE_WAITING;
        updateState(tw);
      }
    } catch (Exception e) {
      log.error("Could not release Trigger: " + trigger.getFullName(), e);
    }
  }

  /**
   * <p>
   * Inform the <code>JobStore</code> that the scheduler is now firing the
   * given <code>Trigger</code> (executing its associated <code>Job</code>),
   * that it had previously acquired (reserved).
   * </p>
   */
  @Override
  public TriggerFiredBundle triggerFired(SchedulingContext ctxt,
      Trigger trigger) {

    log.debug("Fired Trigger: " + trigger.getFullName());
    String key = TriggerWrapper.getTriggerNameKey(trigger);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain, key)
            .withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;
    try {
      tw = triggerFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Could not load Trigger: " + trigger.getFullName());
    }

    // was the trigger deleted since being acquired?
    if (tw == null || tw.trigger == null) {
      return null;
    }
    // was the trigger completed, paused, blocked, etc. since being
    // acquired?
    if (tw.state != TriggerWrapper.STATE_ACQUIRED) {
      return null;
    }

    Calendar cal = null;
    if (tw.trigger.getCalendarName() != null) {
      cal = retrieveCalendar(ctxt, tw.trigger.getCalendarName());
      if (cal == null)
        return null;
    }
    Date prevFireTime = trigger.getPreviousFireTime();
    // call triggered on our copy, and the scheduler's copy
    tw.trigger.triggered(cal);
    trigger.triggered(cal);

    try {
      removeTrigger(ctxt, trigger.getName(), trigger.getGroup());
      storeTrigger(ctxt, trigger, true);
      tw.state = TriggerWrapper.STATE_WAITING;
      updateState(tw);
    } catch (Exception e) {
      log.error("Error while firing Trigger: " + trigger.getFullName());
    }

    TriggerFiredBundle bndle = new TriggerFiredBundle(retrieveJob(ctxt,
        trigger.getJobName(), trigger.getJobGroup()), trigger, cal,
        false, new Date(), trigger.getPreviousFireTime(), prevFireTime,
        trigger.getNextFireTime());

    // JobDetail job = bndle.getJobDetail();
    // TODO: Handle concurrent job execution

    return bndle;
  }

  /**
   * <p>
   * Inform the <code>JobStore</code> that the scheduler has completed the
   * firing of the given <code>Trigger</code> (and the execution its
   * associated <code>Job</code>), and that the
   * <code>{@link org.quartz.JobDataMap}</code> in the given
   * <code>JobDetail</code> should be updated if the <code>Job</code> is
   * stateful.
   * </p>
   */
  @Override
  public void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger,
      JobDetail jobDetail, int triggerInstCode) {
    log.debug("Completing Job: " + trigger.getFullName());

    String jobKey = JobWrapper.getJobNameKey(jobDetail.getName(),
        jobDetail.getGroup());
    GetAttributesResult jobresult = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(jobDomain, jobKey)
            .withConsistentRead(new Boolean(true)));
    JobDetail job = null;
    try {
      job = jobDetailFromAttributes(jobresult.getAttributes());
      if (job.isStateful()) {
        // TODO: Implement support for stateful jobs
      }
    } catch (Exception e) {
      log.error(
          "Could not complete job for Trigger: "
              + trigger.getFullName(), e);
    }

    String triggerKey = TriggerWrapper.getTriggerNameKey(trigger);
    GetAttributesResult result = amazonSimpleDb
        .getAttributes(new GetAttributesRequest(triggerDomain,
            triggerKey).withConsistentRead(new Boolean(true)));
    TriggerWrapper tw = null;

    try {
      tw = triggerFromAttributes(result.getAttributes());
    } catch (Exception e) {
      log.error("Could not find Trigger: " + trigger.getFullName(), e);
    }

    // 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

    // check for trigger deleted during execution...
    if (tw != null) {
      if (triggerInstCode == Trigger.INSTRUCTION_DELETE_TRIGGER) {

        if (trigger.getNextFireTime() == null) {
          // double check for possible reschedule within job
          // execution, which would cancel the need to delete...
          if (tw.getTrigger().getNextFireTime() == null) {
            removeTrigger(ctxt, trigger.getName(),
                trigger.getGroup());
          }
        } else {
          removeTrigger(ctxt, trigger.getName(), trigger.getGroup());
          signaler.signalSchedulingChange(0L);
        }
      } else if (triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE) {
        tw.state = TriggerWrapper.STATE_COMPLETE;
        removeTrigger(ctxt, trigger.getName(), trigger.getGroup());
        signaler.signalSchedulingChange(0L);
      } else if (triggerInstCode == Trigger.INSTRUCTION_SET_TRIGGER_ERROR) {
        getLog().info(
            "Trigger " + trigger.getFullName()
                + " set to ERROR state.");
        tw.state = TriggerWrapper.STATE_ERROR;
        updateState(tw);
        signaler.signalSchedulingChange(0L);
      } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_ERROR) {
        getLog().info(
            "All triggers of Job " + trigger.getFullJobName()
                + " set to ERROR state.");
        setAllTriggersOfJobToState(trigger.getJobName(),
            trigger.getJobGroup(), TriggerWrapper.STATE_ERROR);
        signaler.signalSchedulingChange(0L);
      } else if (triggerInstCode == Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE) {
        setAllTriggersOfJobToState(trigger.getJobName(),
            trigger.getJobGroup(), TriggerWrapper.STATE_COMPLETE);
        signaler.signalSchedulingChange(0L);
      }
    }
  }

  protected void setAllTriggersOfJobToState(String jobName, String jobGroup,
      int state) {
    log.debug("Setting state of all triggers of Job: " + jobName + "."
        + jobGroup);
    Trigger[] triggers = getTriggersForJob(null, jobName, jobGroup);
    for (int i = 0; i < triggers.length; i++) {
      TriggerWrapper tw = new TriggerWrapper(triggers[i]);
      if (state != TriggerWrapper.STATE_WAITING) {
        removeTrigger(null, tw.trigger.getName(), tw.trigger.getGroup());
      } else {
        tw.state = state;
        updateState(tw);
      }
    }
  }

  /**
   * @see org.quartz.spi.JobStore#getPausedTriggerGroups(org.quartz.core.SchedulingContext)
   */
  @Override
  public Set getPausedTriggerGroups(SchedulingContext ctxt)
      throws JobPersistenceException {
    return null;
  }

  @Override
  public void setInstanceId(String schedInstId) {
    //
  }

  @Override
  public void setInstanceName(String schedName) {
    //
  }

  @Override
  public long getEstimatedTimeToReleaseAndAcquireTrigger() {
    return 5;
  }

  @Override
  public boolean isClustered() {
    return false;
  }

}

/*******************************************************************************
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Helper Classes. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * *
*/

class TriggerComparator implements Comparator {

  @Override
  public int compare(Object obj1, Object obj2) {
    TriggerWrapper trig1 = (TriggerWrapper) obj1;
    TriggerWrapper trig2 = (TriggerWrapper) obj2;

    int comp = trig1.trigger.compareTo(trig2.trigger);
    if (comp != 0) {
      return comp;
    }

    comp = trig2.trigger.getPriority() - trig1.trigger.getPriority();
    if (comp != 0) {
      return comp;
    }

    return trig1.trigger.getFullName().compareTo(
        trig2.trigger.getFullName());
  }

  @Override
  public boolean equals(Object obj) {
    return (obj instanceof TriggerComparator);
  }
}

class JobWrapper {

  public String key;

  public JobDetail jobDetail;

  JobWrapper(JobDetail jobDetail) {
    this.jobDetail = jobDetail;
    key = getJobNameKey(jobDetail);
  }

  JobWrapper(JobDetail jobDetail, String key) {
    this.jobDetail = jobDetail;
    this.key = key;
  }

  static String getJobNameKey(JobDetail jobDetail) {
    return jobDetail.getGroup() + "_$x$x$_" + jobDetail.getName();
  }

  static String getJobNameKey(String jobName, String groupName) {
    return groupName + "_$x$x$_" + jobName;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof JobWrapper) {
      JobWrapper jw = (JobWrapper) obj;
      if (jw.key.equals(this.key)) {
        return true;
      }
    }

    return false;
  }

  @Override
  public int hashCode() {
    return key.hashCode();
  }

}

class TriggerWrapper {

  public String key;
  public String jobKey;
  public Trigger trigger;

  public int state = STATE_WAITING;
  public static final int STATE_WAITING = 0;
  public static final int STATE_ACQUIRED = 1;
  public static final int STATE_EXECUTING = 2;
  public static final int STATE_COMPLETE = 3;
  public static final int STATE_PAUSED = 4;
  public static final int STATE_BLOCKED = 5;
  public static final int STATE_PAUSED_BLOCKED = 6;
  public static final int STATE_ERROR = 7;

  TriggerWrapper(Trigger trigger) {
    this.trigger = trigger;
    key = getTriggerNameKey(trigger);
    this.jobKey = JobWrapper.getJobNameKey(trigger.getJobName(),
        trigger.getJobGroup());
  }

  static String getTriggerNameKey(Trigger trigger) {
    return trigger.getGroup() + "_$x$x$_" + trigger.getName();
  }

  static String getTriggerNameKey(String triggerName, String groupName) {
    return groupName + "_$x$x$_" + triggerName;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof TriggerWrapper) {
      TriggerWrapper tw = (TriggerWrapper) obj;
      if (tw.key.equals(this.key)) {
        return true;
      }
    }

    return false;
  }

  @Override
  public int hashCode() {
    return key.hashCode();
  }

  public Trigger getTrigger() {
    return this.trigger;
  }
}
TOP

Related Classes of com.threepillar.labs.quartz.simpledb.SimpleDbJobStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.