package org.jblooming.scheduler;
import org.jblooming.PlatformRuntimeException;
import org.jblooming.utilities.JSP;
import org.jblooming.tracer.Tracer;
import org.jblooming.operator.Operator;
import org.jblooming.oql.OqlQuery;
import org.jblooming.persistence.PersistenceHome;
import org.jblooming.persistence.exceptions.FindException;
import org.jblooming.persistence.exceptions.PersistenceException;
import org.jblooming.persistence.hibernate.HibernateFactory;
import org.jblooming.persistence.hibernate.PersistenceContext;
import java.util.*;
import java.util.concurrent.Future;
import java.io.Serializable;
/**
* @author Pietro Polsinelli ppolsinelli@open-lab.com
* @author Roberto Bicchierai rbicchierai@open-lab.com
*/
public class Scheduler extends TimerTask {
public TreeSet<OrderedJob> toBeExecuted = new TreeSet<OrderedJob>();
public TreeMap<Serializable, FutureIsPink> inExecution = new TreeMap();
public long tick;
private static Scheduler scheduler;
private Timer timer;
public String instantiator;
public Date instantiationTime;
private Set<JobLogData> jobLogsToBeStored = new HashSet();
public static final long DEFAULT_TICK = 600000;
public static int deltaForExcludingRelaunch = 30000;
private Scheduler() {
}
public static void instantiate(long tick, Operator operator) {
instantiate(tick, operator.getDisplayName());
}
public static void instantiate(long tick, String operatorName) {
if (scheduler != null && scheduler.timer != null)
return;
//throw new PlatformRuntimeException("Scheduler and timer already instantiated");
if (scheduler == null) {
scheduler = new Scheduler();
}
scheduler.fillFromPersistence();
scheduler.instantiator = operatorName;
scheduler.instantiationTime = new Date();
scheduler.tick = tick;
scheduler.timer = new Timer(false);
scheduler.timer.schedule(scheduler, new Date(), tick);
}
public static Scheduler getInstance() {
return scheduler;
}
/**
* tick: run due and refills toBeExecuted
*/
public void run() {
boolean exception = true;
PersistenceContext pc = null;
try {
//must be new, not get default as it is called in same thread by loader support
pc = new PersistenceContext();
SortedSet<OrderedJob> toBeExecutedJobs = new TreeSet(toBeExecuted.headSet(new OrderedJob(new Date().getTime() - tick + 1, -1)));
for (OrderedJob orderedJob : new TreeSet<OrderedJob>(toBeExecutedJobs)) {
Serializable id = orderedJob.jobId;
Job job = (Job) PersistenceHome.findByPrimaryKey(Job.class, id, pc);
if (job != null && job.isEnabled()) {
//is running ?
if (inExecution.keySet().contains(job.getId())) {
Scheduler.FutureIsPink fip = inExecution.get(job.getId());
Future future = fip.future;
boolean stillRunning = !future.isDone();
//check still running in timeout
if (stillRunning) {
if (System.currentTimeMillis() > fip.expireTime) {
future.cancel(true);
Tracer.jobLogger.error(job.getName()+": went in timeout, cancelled ");
}
}
boolean failedAsJob = false;
Iterator i = jobLogsToBeStored.iterator();
while (i.hasNext()) {
JobLogData jobLogData = (JobLogData) i.next();
if (jobLogData.id.equals(job.getId()))
failedAsJob = !jobLogData.successfull;
}
boolean failedAsThread = future.isCancelled();
if (failedAsJob)
Tracer.jobLogger.error(job.getName()+": job failed as job");
if (failedAsThread)
Tracer.jobLogger.error(job.getName()+": job failed as thread");
//completed successfully as thread and as job
if (!stillRunning && !failedAsJob) {
inExecution.remove(job.getId());
toBeExecuted.remove(orderedJob);
//eventually put in relaunch queue
Date dateAfter = job.getSchedule().getNextDateAfter(new Date(System.currentTimeMillis()));
if (dateAfter != null) {
toBeExecuted.add(new OrderedJob(dateAfter.getTime(), job.getId()));
}
}
//failed
if (!stillRunning && (failedAsJob || failedAsThread)) {
if (!job.isOnErrorSuspendScheduling()) {
if (!job.isOnErrorRetryNow()) {
inExecution.remove(job.getId());
rescheduleJob(orderedJob, job);
} else
//retry now case need not be handled, as job is not removed from toBeExecuted
Tracer.jobLogger.error(job.getName()+": job retried");
} else {
//suspended
inExecution.remove(job.getId());
toBeExecuted.remove(orderedJob);
}
}
// otherwise is still running -> do nothing
} else {
// this in order to avoid a second node (server) to launch exactly the same job for the same scheduled launch
long lastExecutionTime = job.getLastExecutionTime();
long ctm = System.currentTimeMillis();
long delta = Math.abs(lastExecutionTime - ctm);
// this in order to avoid a second node (server) to launch exactly the same job for the same scheduled launch
if (delta > deltaForExcludingRelaunch) {
job.resetLastExecutionTime();
job.store(pc);
pc.checkPoint();
runAJob(job);
} else {
//compute next execution time
rescheduleJob(orderedJob, job);
Tracer.platformLogger.debug("Job " + job.getName() + " not launched as found launched by another node");
}
}
}
}
//store the jobLogs
while (true) {
if (jobLogsToBeStored.size() > 0) {
HashSet<JobLogData> datas = new HashSet<JobLogData>(jobLogsToBeStored);
if (datas.size() > 0) {
JobLogData jl = datas.iterator().next();
Job job = (Job) PersistenceHome.findByPrimaryKey(Job.class, jl.id, pc);
long howLongPiuOMenTheTick = System.currentTimeMillis() - (job.getLastExecutionTime() + tick);
howLongPiuOMenTheTick = howLongPiuOMenTheTick < 0 ? howLongPiuOMenTheTick + tick : howLongPiuOMenTheTick;
Tracer.jobLogger.info("job: " + job.getName() + " executed at " + jl.date + " successful:" + jl.successfull + " duration:" + howLongPiuOMenTheTick + "ms. notes: " + jl.notes);
jobLogsToBeStored.remove(jl);
}
} else
break;
}
//check those in timeout
for (Serializable jid : new HashSet<Serializable>(inExecution.keySet())) {
FutureIsPink fip = inExecution.get(jid);
Future future = fip.future;
boolean stillRunning = !future.isDone();
//check still running in timeout
if (stillRunning) {
if (System.currentTimeMillis() > fip.expireTime) {
future.cancel(true);
inExecution.remove(jid);
Tracer.jobLogger.error("Job id:" + jid + " went in timeout, cancelled ");
}
}
}
exception = false;
pc.commitAndClose();
} catch (Throwable t) {
if (pc!=null)
pc.rollbackAndClose();
//throw new PlatformRuntimeException(e);
Tracer.desperatelyLog("Scheduler failed a run " + JSP.w(t.getMessage()), false, t);
Tracer.jobLogger.error(t);
Tracer.platformLogger.error(t);
}
}
private void rescheduleJob(OrderedJob orderedJob, Job job) {
toBeExecuted.remove(orderedJob);
Date dateAfter = job.getSchedule().getNextDateAfter(new Date(System.currentTimeMillis()));
if (dateAfter != null) {
toBeExecuted.add(new OrderedJob(dateAfter.getTime(), job.getId()));
Tracer.jobLogger.error(job.getName()+": job rescheduled");
}
}
public void runAJob(Job job) {
Future future = PlatformExecutionService.executorService.submit(job);
FutureIsPink fip = new FutureIsPink();
fip.future = future;
fip.startTime = System.currentTimeMillis();
fip.expireTime = job.getTimeoutTime() > 0 ? System.currentTimeMillis() + job.getTimeoutTime() : Long.MAX_VALUE;
inExecution.put(job.getId(), fip);
}
public void addJob(Job job) {
fillFromPersistence();
}
public void removeJob(Job job) {
scheduler.toBeExecuted.remove(new OrderedJob(0, job.getId()));
}
public synchronized void fillFromPersistence() {
synchronized (this) {
PersistenceContext pc = null;
try {
pc = new PersistenceContext();
String hql = "from " + Job.class.getName() + " as job where job.schedule.end >= :now and job.enabled = :istrue";
OqlQuery q = new OqlQuery(hql, pc);
q.getQuery().setTimestamp("now", new Date(System.currentTimeMillis()));
q.getQuery().setBoolean("istrue", true);
scheduler.toBeExecuted = new TreeSet<OrderedJob>();
List<Job> persJobs = q.list();
for (Job job : persJobs) {
Date dateAfter = job.getSchedule().getNextDateAfter(new Date(System.currentTimeMillis()));
if (dateAfter != null) {
Serializable id = job.getId();
scheduler.toBeExecuted.add(new OrderedJob(dateAfter.getTime(), id));
}
}
pc.commitAndClose();
} catch (Throwable e) {
if (pc != null)
pc.rollbackAndClose();
throw new PlatformRuntimeException(e);
}
}
}
public static boolean isRunning() {
boolean sched = scheduler != null;
return sched && scheduler.timer != null;
}
public void stop() {
if (scheduler != null) {
scheduler.cancel();
if (scheduler.timer != null)
scheduler.timer.cancel();
scheduler.timer = null;
scheduler = null;
}
}
public synchronized void addJobLogData(JobLogData jobLog) {
jobLogsToBeStored.add(jobLog);
}
public class OrderedJob implements Comparable {
public long exeTime;
public Date exeTimeDate;
public Serializable jobId;
public OrderedJob(long exeTime, Serializable jobId) {
this.exeTime = exeTime;
this.exeTimeDate = new Date(exeTime);
this.jobId = jobId;
}
public int compareTo(Object o) {
int result = (new Long(exeTime).compareTo(new Long(((OrderedJob) o).exeTime)));
if (result == 0 && !this.equals(o))
result = -1;
return result;
}
public boolean equals(Object o) {
return jobId == ((OrderedJob) o).jobId;
}
public int hashCode() {
return jobId.hashCode();
}
}
public class FutureIsPink {
public Future future;
public long expireTime;
public long startTime;
}
}