/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.astro.internal.job;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.impl.matchers.GroupMatcher.jobGroupEquals;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimerTask;
import org.openhab.binding.astro.AstroBindingProvider;
import org.openhab.binding.astro.internal.bus.PlanetPublisher;
import org.openhab.binding.astro.internal.common.AstroConfig;
import org.openhab.binding.astro.internal.common.AstroContext;
import org.openhab.binding.astro.internal.config.AstroBindingConfig;
import org.openhab.binding.astro.internal.model.PlanetName;
import org.openhab.binding.astro.internal.model.Season;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
import org.openhab.binding.astro.internal.util.DelayedExecutor;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Main class for scheduling the different jobs.
*
* @author Gerhard Riegler
* @since 1.5.0
*/
public class JobScheduler {
private static final Logger logger = LoggerFactory.getLogger(JobScheduler.class);
private static final String JOB_GROUP = "Astro";
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private AstroContext context;
private Scheduler scheduler;
private DelayedExecutor delayedExecutor = new DelayedExecutor();
public JobScheduler(AstroContext context) {
try {
this.context = context;
scheduler = StdSchedulerFactory.getDefaultScheduler();
} catch (SchedulerException ex) {
logger.error(ex.getMessage(), ex);
}
}
/**
* Restarts the JobScheduler after a short delay.
*/
public void restart() {
delayedExecutor.cancel();
delayedExecutor.schedule(new TimerTask() {
@Override
public void run() {
stop();
start();
}
}, 3000);
}
/**
* Start the jobs if a binding is available.
*/
public void start() {
startAndScheduleDailyJob();
if (isBindingForIntervalJobAvailable()) {
if (context.getConfig().getInterval() > 0) {
scheduleIntervalJob();
} else {
logger.warn(
"Sun azimuth/elevation and/or moon distance/illumination binding available, but configuration is disabled (interval = 0)!");
}
}
}
/**
* Stops all scheduled jobs and clears the PlanetPublisher cache.
*/
public void stop() {
try {
for (JobKey jobKey : scheduler.getJobKeys(jobGroupEquals(JOB_GROUP))) {
logger.info("Deleting astro job: " + jobKey.getName());
scheduler.deleteJob(jobKey);
}
} catch (SchedulerException ex) {
logger.error(ex.getMessage(), ex);
}
PlanetPublisher.getInstance().clear();
}
/**
* Checks if a binding for the IntervalJob is available.
*/
private boolean isBindingForIntervalJobAvailable() {
List<AstroBindingConfig> intervalBindings = new ArrayList<AstroBindingConfig>();
intervalBindings.add(new AstroBindingConfig(PlanetName.SUN, "position", "azimuth"));
intervalBindings.add(new AstroBindingConfig(PlanetName.SUN, "position", "elevation"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "distance", "kilometer"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "distance", "miles"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "distance", "date"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "phase", "illumination"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "zodiac", "sign"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "position", "azimuth"));
intervalBindings.add(new AstroBindingConfig(PlanetName.MOON, "position", "elevation"));
for (AstroBindingProvider provider : context.getProviders()) {
for (AstroBindingConfig astroBindingConfig : intervalBindings) {
if (provider.hasBinding(astroBindingConfig)) {
return true;
}
}
}
return false;
}
/**
* Schedules a daily job at midnight for astro calculation and starts it
* immediately too.
*/
public void startAndScheduleDailyJob() {
String jobName = DailyJob.class.getSimpleName();
CronTrigger cronTrigger = newTrigger().withIdentity(jobName + "-Trigger", JOB_GROUP).startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")).build();
schedule(jobName, DailyJob.class, cronTrigger, new JobDataMap());
logger.info("Scheduled a daily job at midnight for astro calculation");
Trigger trigger = newTrigger().withIdentity(jobName + "-StartupTrigger", JOB_GROUP).startNow().build();
schedule(jobName + "-Startup", DailyJob.class, trigger, new JobDataMap());
}
/**
* Schedules the IntervalJob with the specified interval and starts it
* immediately.
*/
public void scheduleIntervalJob() {
AstroConfig config = context.getConfig();
String jobName = IntervalJob.class.getSimpleName();
Date start = new Date(System.currentTimeMillis() + (config.getInterval()) * 1000);
Trigger trigger = newTrigger().withIdentity(jobName + "-Trigger", JOB_GROUP).startAt(start)
.withSchedule(simpleSchedule().repeatForever().withIntervalInSeconds(config.getInterval())).build();
schedule(jobName, IntervalJob.class, trigger, new JobDataMap());
logger.info("Scheduled astro job with interval of {} seconds", config.getInterval());
}
/**
* Schedules next Season job.
*/
public void scheduleSeasonJob(Season season) {
Calendar nextSeason = season.getNextSeason();
if (nextSeason == null) {
nextSeason = DateTimeUtils.getFirstDayOfNextYear();
}
schedule(nextSeason, "Season", new JobDataMap(), SeasonJob.class);
}
/**
* Schedules a item job at the specified date/time from the calendar object.
*/
public void scheduleItem(Calendar calendar, String itemName) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("itemName", itemName);
schedule(calendar, itemName, jobDataMap, ItemJob.class);
}
/**
* Schedules a job at the specified date/time, deletes a previously
* scheduled job.
*/
private void schedule(Calendar calendar, String jobName, JobDataMap jobDataMap, Class<? extends Job> jobClass) {
if (System.currentTimeMillis() < calendar.getTimeInMillis()) {
try {
JobKey jobKey = new JobKey(jobName, JOB_GROUP);
if (scheduler.getJobDetail(jobKey) != null) {
scheduler.deleteJob(jobKey);
}
Trigger trigger = newTrigger().withIdentity(jobName + "-Trigger", JOB_GROUP).startAt(calendar.getTime())
.build();
JobDetail jobDetail = newJob(jobClass).withIdentity(jobKey).usingJobData(jobDataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
logger.debug("Scheduled job with name {} at {}", jobName, sdf.format(calendar.getTime()));
} catch (SchedulerException ex) {
logger.error(ex.getMessage(), ex);
}
} else {
logger.debug("Skipping job with name {} for today, starttime is in the past", jobName);
}
}
/**
* Schedules a job by trigger.
*/
private void schedule(String jobName, Class<? extends Job> job, Trigger trigger, JobDataMap jobDataMap) {
try {
JobDetail jobDetail = newJob(job).withIdentity(jobName, JOB_GROUP).usingJobData(jobDataMap).build();
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException ex) {
logger.error(ex.getMessage(), ex);
}
}
}