/*
* Copyright 2009 NCHOVY
*
* 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.
*/
package org.krakenapps.cron.impl;
import java.util.*;
import java.util.Map.Entry;
import org.krakenapps.cron.Schedule;
import org.osgi.framework.InvalidSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Cron scheduler that checks and runs registered cron jobs. singleton class.
*
* @author periphery
* @since 1.0.0
*/
public class Scheduler {
private final Logger logger = LoggerFactory.getLogger(CronConfig.class.getName());
private PriorityQueue<Job> queue;
private final Thread loop = new Thread(new Loop(), "Cron Scheduler");
private boolean running;
public void start(Map<Integer, Schedule> map) {
queue = reset(map);
running = true;
loop.start();
}
public void stop() {
running = false;
}
private PriorityQueue<Job> reset(Map<Integer, Schedule> map) {
PriorityQueue<Job> tempQueue = new PriorityQueue<Job>();
for (Entry<Integer, Schedule> en : map.entrySet()) {
Job job = new Job(en.getKey().intValue(), en.getValue());
tempQueue.add(job);
}
return tempQueue;
}
/**
* insert the schedule to the scheduling queue.
*
* @param id
* id of the new schedule
* @param sche
* new schedule to add
*/
public void put(int id, Schedule sche) {
Job job = new Job(id, sche);
synchronized (queue) {
queue.add(job);
}
}
/**
* delete the schedule according to the given id from the scheduling queue.
*
* @param id
* id of the deleting schedule
*/
public void remove(int id) {
synchronized (queue) {
for (Object job : queue.toArray()) {
if (((Job) job).getScheduleId() == id)
queue.remove(job);
}
}
}
/**
* list jobs in the scheduling queue.
*
* @return list of scheduling queue entries.
*/
public List<String> getJobList() {
Object[] sorted = queue.toArray();
Arrays.sort(sorted);
List<String> result = new ArrayList<String>();
for (Object job : sorted) {
result.add(((Job) job).toString());
}
return result;
}
/**
* this class is used as a thread that periodically checks and runs
* scheduled tasks. the thread busy waits, sleeping for 10 seconds and
* checks whether the first task in the scheduling queue is time for
* execution. if so, it generates child thread to run the task. If the
* 'Schedule.running' is set to false when this thread wakes up, the thread
* terminates itself, meaning that there can be time gap between setting the
* 'Scheduler.running' to false and the actual stopping of the loop thread.
*
* @author periphery
*
*/
private class Loop implements Runnable {
private int SLEEP_TIME = 10; // sec.
@Override
public void run() {
logger.info("Cron: scheduler started");
loop();
}
private void loop() {
while (running) {
checkAndRun();
try {
Thread.sleep(this.SLEEP_TIME * 1000); // sleep for 10 sec
} catch (InterruptedException e) {
logger.warn("Cron: scheduler Thread.sleep error.", e);
}
}
logger.info("Cron: scheduler stopped");
}
private void checkAndRun() {
synchronized (queue) {
if (itIsTime()) {
Job first = queue.poll();
Thread runner = new Thread(new Runner(first.clone()), "Cron Runner");
runner.start();
// set base time as 1 min after current time
first.setNextOccurence(new Date(new Date().getTime() + 60 * 1000));
queue.add(first);
// recursive call to check next Job
checkAndRun();
}
}
}
public boolean itIsTime() {
try {
return queue.peek().isTimeToDo();
} catch (Exception e) { // empty queue
return false;
}
}
}
private class Runner implements Runnable {
private final Job job;
public Runner(Job job) {
this.job = job;
}
@Override
public void run() {
try {
logger.debug("Cron: run registered task " + job);
job.run();
} catch (NullPointerException e) {
logger.debug("Cron: unable to run " + job + ". runnable \'" + job.schedule.getTaskName() + "\' is not active.");
} catch (InvalidSyntaxException e) {
logger.warn("Cron: scheduler instance.name syntax error.", e);
}
}
}
}