/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.scheduler.common;
import org.joda.time.DateTime;
import divconq.hub.Hub;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationObserver;
import divconq.scheduler.ISchedule;
import divconq.scheduler.limit.LimitHelper;
import divconq.struct.RecordStruct;
import divconq.util.TimeUtil;
import divconq.work.Task;
import divconq.xml.XElement;
// TODO respect Hub idled
public class CommonSchedule extends OperationObserver implements ISchedule {
static public final int METHOD_NONE = 0;
static public final int METHOD_STANDARD = 1;
static public final int METHOD_SCRIPT = 2;
static public final int METHOD_CLASS = 3;
// what method is used to calculate the run times
protected int method = CommonSchedule.METHOD_NONE;
protected IScheduleHelper helper = null;
// limits handler
protected LimitHelper limits = new LimitHelper();
// when was this last run, leave null if not important
protected DateTime last = null;
// the code to run on schedule
protected Task task = null;
protected IInlineScript iscript = null;
protected Object userData = null;
protected RecordStruct hints = new RecordStruct();
protected boolean canceled = false;
public void setLastRun(DateTime v) {
this.last = v;
}
public DateTime getLastRun() {
return this.last;
}
public void setInlineScript(IInlineScript v) {
this.iscript = v;
}
public IInlineScript getInlineScript() {
return this.iscript;
}
public void setTask(Task v) {
this.task = v;
}
@Override
public Task task() {
return this.task;
}
public void setUserData(Object v) {
this.userData = v;
}
public Object getUserData() {
return this.userData;
}
@Override
public RecordStruct getHints() {
return this.hints;
}
public void setHint(String name, String value) {
this.hints.setField(name, value);
}
/*
* <CommonSchedule
* Method="None,Standard,Script,Class"
* View="Period,Daily,Weekly,Monthly,Script,Custom" - for the UI to determine which pane to show
* ClassName="n" - use the bundle provided, if any, to load the class
* - must implement IScheduleHelper
* >
* <Limits ... /> - see LimitHelper
*
* // use ISO periods, e.g. PT2H30M10S
* // used for intra-daily mostly, but can be any
* <Period Value="n" />
*
* // if method = Daily, these are the times to run, ignore frequency
* <Daily>
* <Schedule At="" RunIfMissed="True/False" />
* <Schedule At="" RunIfMissed="True/False" />
* <Schedule At="" RunIfMissed="True/False" />
* </Daily>
*
* // if method = Weekly, these are the days to run. may be more than one WeekDays, use first match
* <Weekly>
* <Weekdays Monday="T/F" Tuesday="n" ... All="T/F" >
* <Schedule At="" RunIfMissed="True/False" />
* </Weekdays>
* </Weekly>
*
* // if method = "Monthly" (may be more than Months, etc)
* // excludes for monthly don't make sense, but are there
* <Monthly>
* <Months January="T/F" ... All="T/F" >
* <First Monday="T/F" Tuesday="n" ... All="T/F" >
* <Schedule At="" RunIfMissed="True/False" />
* </First>
* <Second Monday="T/F" Tuesday="n" ... All="T/F" >
* <Schedule At="" RunIfMissed="True/False" />
* </Second>
* ... etc, or ...
* <Monthday List="N,N,N,Last">
* <Schedule At="" RunIfMissed="True/False" />
* </Monthday>
* </Months>
* </Monthly>
*
* // _last is available, but = null if first run
* // _now is available
* // if method != Script then there is an obj _suggested
* // to call "suggest.next()" that will provide next based
* // on the method settings (so script is more a filter)
* // when method is script then no hints are provided
* <Script> run when scheduling next </Script>
* </CommonSchedule>
*/
public void init(XElement config) {
// TODO load config, if classes are involved then use custom loader if available
if (config != null) {
this.limits.init(config.find("Limits"));
// what method is used to calculate the run times
String meth = config.getAttribute("Method", "Standard");
XElement helpel = null;
if ("Standard".equals(meth)) {
this.method = CommonSchedule.METHOD_STANDARD;
helpel = config.find("Period");
if (helpel != null) {
this.helper = new PeriodHelper();
}
else {
helpel = config.find("Daily");
if (helpel != null) {
this.helper = new DailyHelper();
}
else {
helpel = config.find("Weekly");
if (helpel != null) {
this.helper = new WeekdayHelper();
}
else {
helpel = config.find("Monthly");
if (helpel != null) {
this.helper = new MonthHelper();
}
else {
// TODO log
System.out.println("schedule does not appear to have a helper");
}
}
}
}
}
else if ("Script".equals(meth)) {
this.method = CommonSchedule.METHOD_SCRIPT;
XElement sel = config.find("Script");
if (sel != null) {
/* TODO
String code = sel.getText();
if (!StringUtil.isBlank(code)) {
this.script = new Script();
try {
this.script.setScript(code);
}
catch (Exception e) {
// TODO log
}
}
// TODO
*
*/
}
}
else if ("Class".equals(meth)) {
this.method = CommonSchedule.METHOD_CLASS;
String className = config.getAttribute("ClassName");
try {
// TODO
//this.helper = (IScheduleHelper) ((this.customLoader != null)
// ? this.customLoader.getInstance(className)
// : Class.forName(className).newInstance());
this.helper = (IScheduleHelper) Hub.instance.getInstance(className);
helpel = config;
}
catch (Exception e) {
// TODO log
System.out.println("unable to load schedule helper class: " + className);
}
}
if (this.helper != null) {
this.helper.setLimits(this.limits);
this.helper.setLast(this.last);
this.helper.init(this, helpel);
}
}
// setup the first run
this.reschedule();
}
public void init(IScheduleHelper helper, XElement limits, XElement hconfig) {
// TODO load config, if classes are involved then use custom loader if available
this.limits.init(limits);
this.method = CommonSchedule.METHOD_CLASS;
this.helper = helper;
this.helper.setLimits(this.limits);
this.helper.setLast(this.last);
this.helper.init(this, hconfig);
// setup the first run
this.reschedule();
}
// same as reschedule, except we must move forward at least one day
public boolean rescheduleOnNextDate() {
this.last = TimeUtil.nextDayAtMidnight(this.last);
return this.reschedule();
}
@Override
public boolean reschedule() {
// it is important to remove the old observers because we are going to add a new one - us - and the others
//if (this.task != null)
// this.task.clearObservers();
if (this.helper != null) {
// TODO if script then use that to help with finding next
this.last = this.helper.next();
//System.out.println("rescheduled: " + ((this.last == null) ? "null" : this.last.toString()));
return (this.last != null);
}
/* TODO
else if (this.script != null) {
this.script.addVariable("_now", new DateTime());
this.script.addVariable("_last", this.last);
this.script.addVariable("_sched", this);
this.script.addVariable("_data", this.userData);
try {
Object o = this.script.call("schedule"); // TODO consider passing params rather than set vars above
if (o instanceof DateTime)
this.last = (DateTime)o;
else
System.out.println("bad return value: " + o); // TODO log
}
catch (Exception x) {
System.out.println("bad return value: " + x); // TODO log
}
System.out.println("rescheduled script: " + ((this.last == null) ? "null" : this.last.toString()));
return (this.last != null);
}
*/
else if (this.iscript != null) {
try {
this.last = this.iscript.schedule(this); // TODO consider passing params rather than set vars above
}
catch (Exception x) {
System.out.println("bad return value: " + x); // TODO log
}
System.out.println("rescheduled script: " + ((this.last == null) ? "null" : this.last.toString()));
return (this.last != null);
}
return false;
}
@Override
public long when() {
if ((this.task != null) && (this.last != null))
return this.last.getMillis();
return -1;
}
@Override
public void cancel() {
this.canceled = true;
// TODO someday optimize by removing from scheduler node list
}
@Override
public boolean isCanceled() {
return this.canceled;
}
@Override
public void completed(OperationContext ctx) {
// remember - we can look at the task for further info when rescheduling
// run.getResult();
if (this.reschedule()) {
//this.task.reset();
Hub.instance.getScheduler().addNode(this);
}
}
@Override
public void log(OperationContext ctx, RecordStruct entry) {
}
@Override
public void step(OperationContext ctx, int num, int of, String name) {
}
@Override
public void progress(OperationContext ctx, String msg) {
}
@Override
public void amount(OperationContext ctx, int v) {
}
@Override
public void prep(OperationContext ctx) {
}
@Override
public void start(OperationContext ctx) {
}
@Override
public void stop(OperationContext ctx) {
}
}