All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nger.akka-quartz-scheduler_2.11.1.8.0-akka-2.5.x.source-code.QuartzSchedules.scala Maven / Gradle / Ivy

There is a newer version: 1.8.1-akka-2.5.x
Show newest version
package com.typesafe.akka.extension.quartz

import com.typesafe.config.{ConfigObject, ConfigException, Config}
import java.util.TimeZone
import java.util.Date
import scala.util.control.Exception._
import org.quartz._
import collection.immutable
import java.text.ParseException

import scala.collection.JavaConverters._

/**
 * This is really about triggers - as the "job" is roughly defined in the code that
 *  refers to the trigger.
 *
 *  I call them Schedules to get people not thinking about Quartz in Quartz terms (mutable jobs, persistent state)
 *
 *  All jobs "start" immediately.
 */
object QuartzSchedules {
  // timezone (parseable) [optional, defaults to UTC]
  // calendars = list of calendar names that "modify" this schedule
  // description = an optional description of the job [string] [optional]
  // expression = cron expression complying to Quartz' Cron Expression rules.
  // TODO - Misfire Handling

  val catchMissing = catching(classOf[ConfigException.Missing])
  val catchWrongType = catching(classOf[ConfigException.WrongType])
  val catchParseErr = catching(classOf[ParseException])

  def apply(config: Config, defaultTimezone: TimeZone): immutable.Map[String, QuartzSchedule] = catchMissing opt {
    /** The extra toMap call is because the asScala gives us a mutable map... */
    config.getConfig("schedules").root.asScala.toMap.flatMap {
      case (key, value: ConfigObject) =>
        Some(key -> parseSchedule(key, value.toConfig, defaultTimezone))
      case _ =>
        None
    }
  } getOrElse immutable.Map.empty[String, QuartzSchedule]

  def parseSchedule(name: String, config: Config, defaultTimezone: TimeZone): QuartzSchedule = {
    // parse common attributes
    val timezone = catchMissing opt {
      TimeZone.getTimeZone(config.getString("timezone")) // todo - this is bad, as Java silently swaps the timezone if it doesn't match...
    } getOrElse defaultTimezone

    val calendar = catchMissing opt {
      Option(config.getString("calendar")) // TODO - does Quartz validate for us that a calendar referenced is valid/invalid?
    } getOrElse None

    val desc = catchMissing opt {
      config.getString("description")
    }

    parseCronSchedule(name, desc, config)(timezone, calendar)
  }

  def parseCronSchedule(name: String, desc: Option[String], config: Config)(tz: TimeZone, calendar: Option[String]): QuartzCronSchedule = {
    val expression = catchMissing or catchWrongType either { config.getString("expression") } match {
      case Left(t) =>
        throw new IllegalArgumentException("Invalid or Missing Configuration entry 'expression' for Cron Schedule '%s'. You must provide a valid Quartz CronExpression.".format(name), t)
      case Right(str) => catchParseErr either new CronExpression(str) match {
        case Left(t) =>
          throw new IllegalArgumentException("Invalid 'expression' for Cron Schedule '%s'. Failed to validate CronExpression.".format(name), t)
        case Right(expr) => expr
      }
    }
    new QuartzCronSchedule(name, desc, expression, tz, calendar)
  }
}

sealed trait QuartzSchedule {
  type T <: Trigger

  def name: String

  def description: Option[String]

  // todo - I don't like this as we can't guarantee the builder's state, but the Quartz API forces our hand
  def schedule: ScheduleBuilder[T]

  //The name of the optional exclusion calendar to use.
  //NOTE: This formerly was "calendars" but that functionality has since been removed as Quartz never supported more
  //than one calendar anyways.
  def calendar: Option[String]

  /**
    * Utility method that builds a trigger with the data this schedule contains, given a name.
    * Job association can happen separately at schedule time.
    *
    * @param name The name of the job / schedule.
    * @param futureDate The Optional earliest date at which the job may fire.
    * @return The new trigger instance.
    */
  def buildTrigger(name: String, futureDate: Option[Date] = None): T = {
    val partialTriggerBuilder = TriggerBuilder.newTrigger()
                           .withIdentity(name + "_Trigger")
                           .withDescription(description.orNull)
                           .withSchedule(schedule)
                           
    var triggerBuilder = futureDate match {
      case Some(fd) => partialTriggerBuilder.startAt(fd)
      case None => partialTriggerBuilder.startNow()
    }

    triggerBuilder = calendar.map(triggerBuilder.modifiedByCalendar).getOrElse(triggerBuilder)
    triggerBuilder.build()
  }

}

final class QuartzCronSchedule(val name: String,
                               val description: Option[String] = None,
                               val expression: CronExpression,
                               val timezone: TimeZone,
                               val calendar: Option[String] = None) extends QuartzSchedule {

  type T = CronTrigger

  // Do *NOT* build, we need the uncompleted builder. I hate the Quartz API, truly.
  val schedule = CronScheduleBuilder.cronSchedule(expression).inTimeZone(timezone)
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy