protected void schedule(Task task, Pair<String, Trigger> triggerAndExpression) {
   if (triggerAndExpression == null) {
     throw new IllegalArgumentException("Must specify a Trigger and its expression");
   }
   LOGGER.info(
       "Scheduling task [{}] with trigger expression [{}]",
       task.id,
       triggerAndExpression.getFirst());
   task.execution = taskScheduler.schedule(task.runnable, triggerAndExpression.getSecond());
   task.executingTriggerExpression = triggerAndExpression.getFirst();
   task.executingTrigger = triggerAndExpression.getSecond();
 }
  /**
   * Reads latest config from {@link ConfigService} for the given {@link Task} and caches it in that
   * {@link Task}. If config is found but can't be parsed, the {@link Task}'s {@code configured*}
   * fields will include the bad {@link Trigger} expression and a {@link BadConfigTrigger}. If
   * config simply can't be read at all, e.g. network outage, that will just be logged and the
   * {@link Task}'s config will be unchanged.
   *
   * <p>This also handles initialization of the {@link Task's} default {@link Trigger} <em>every
   * time</em>. Which means you can technically change the default and it will be picked up.
   *
   * @param task
   * @return
   * @throws BadTriggerConfigException if the default configuration for the given {@link Task} is
   *     broken. Exceptions during read of latest config from {@link ConfigService} are caught and
   *     handled.
   */
  protected void mergeLatestTriggerConfig(Task task) throws BadTriggerConfigException {

    // Null will mean "have no idea what the latest config would be or
    // if it even exists." Everything else will mean "either found good
    // config, use it, or found bad config and it's up to you what to do
    // with it."
    Pair<String, Trigger> newTriggerConfig = null;
    try {
      newTriggerConfig = readNewTriggerConfig(task.triggerExpressionConfigName);
    } catch (BadTriggerConfigException e) {
      LOGGER.warn(
          "Unable to parse task [{}] trigger config named [{}].",
          new Object[] {task.id, task.triggerExpressionConfigName, e});
      newTriggerConfig = new Pair<String, Trigger>(null, new BadConfigTrigger(e.getConfig(), e));
    } catch (RuntimeException e) {
      // Probably db connection or other systemic issue issue. Will end up
      // leaving this task alone.
      LOGGER.error(
          "Unable to read trigger config named [{}]. This"
              + " was probably a transient and/or systemic issue "
              + " rather than a parse issue.",
          task.triggerExpressionConfigName,
          e);
    }

    if (newTriggerConfig != null && newTriggerConfig.getSecond() == null) {
      // Really a programmer error but let's just quietly normalize to
      // simplify boolean expressions below.
      newTriggerConfig = null;
    }

    if (newTriggerConfig == null) {
      task.configuredTriggerExpression = null;
      task.configuredTrigger = null;
    } else {
      task.configuredTriggerExpression = newTriggerConfig.getFirst();
      task.configuredTrigger = newTriggerConfig.getSecond();
    }

    // Do this every time to avoid weird stuff from mis-use where the
    // expression and trigger don't match.
    task.defaultTrigger = parseTriggerConfig(task.defaultTriggerExpression);
  }
  protected void maybeReschedule(Task task) {
    Pair<String, Trigger> configuredOrDefault = configuredOrDefaultTrigger(task);
    Pair<String, Trigger> newTrigger = null;
    if (task.executingTrigger == null) {

      // first time execution
      newTrigger = configuredOrDefault;
      LOGGER.debug(
          "Preparing to schedule task [{}] for"
              + " first-time execution with trigger expression [{}]",
          task.id,
          newTrigger.getFirst());

    } else if (configuredOrDefault.getSecond() instanceof BadConfigTrigger) {

      // broken config. nothing to do.
      LOGGER.info(
          "Skipping scheduling for task [{}] because it has" + " a bad trigger expression [{}]",
          task.id,
          configuredOrDefault.getFirst());

    } else if (!(configuredOrDefault.getFirst().equals(task.executingTriggerExpression))) {

      // schedule change!
      newTrigger = configuredOrDefault;
      LOGGER.debug(
          "Preparing to re-schedule task [{}] with trigger"
              + " expression [{}]. Previous expression: [{}]",
          new Object[] {task.id, newTrigger.getFirst(), task.executingTriggerExpression});

    } else {
      LOGGER.debug(
          "Skipping scheduling for task [{}] because no"
              + " changes have been requested. Currently executing"
              + " trigger expression: [{}]",
          task.id,
          task.executingTriggerExpression);
      return;
    }

    cancel(task);
    schedule(task, newTrigger);
  }