divconq.scheduler.limit.LimitHelper.java Source code

Java tutorial

Introduction

Here is the source code for divconq.scheduler.limit.LimitHelper.java

Source

/* ************************************************************************
#
#  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.limit;

import java.util.ArrayList;
import java.util.List;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalTime;

import divconq.hub.Hub;
import divconq.util.StringUtil;
import divconq.util.TimeUtil;
import divconq.xml.XElement;

// artificially limit expiration at 10 years, if none present
// server should be restarted at least once every 10 years :)
public class LimitHelper {
    protected DayWindow dailyWindow = new DayWindow();
    protected DateTime validFrom = null;
    protected DateTime validTo = null;
    protected DateTimeZone zone = null;

    // use only for next/check/blocked/open - do not inherit init/start/end
    protected LimitHelper parent = null;

    protected List<MonthWindow> monthly = new ArrayList<MonthWindow>();

    protected List<WeekdayWindow> weekly = new ArrayList<WeekdayWindow>();

    public DayWindow getDailyWindow() {
        return this.dailyWindow;
    }

    /*
     *         <Limits 
     *            LinkBatch="None,Small,Medium,Large"   - include limits defined at server level (batch processing)
     *            DefaultWindow="T/F"         - by default you have 24 hours enabled
           ValidFrom="iso-date-time"   - schedule only before/after these time 
           ValidTo="iso-date-time"
           TimeZone="name"            - zone to apply to the limits
     *         >           
     *     
     *           // one or more windows during which it is ok to run the scheduled work
     *           // defaults to beginning of day to end of day, no matter 
     *           <IncludeWindow From="00:00" To="24:00" />
     *  
     *           <ExcludeWindow From="04:15" To="04:17" />
     *  
     *            <Weekdays Monday="T/F" Tuesday="n" ... All="T/F" >
     *               // if exclude is not present, then assume entire day
     *               <ExcludeWindow From="" To="" />
     *            </Weekdays>
     *
     *           <Months January="T/F" ... >
     *              <First Monday="T/F" Tuesday="n" ... All="T/F" >
     *                  <ExcludeWindow From="" To="" />
     *              </First>
     *              <Second Monday="T/F" Tuesday="n" ... All="T/F" >
     *                  <ExcludeWindow From="" To="" />
     *              </Second>
     *              ... etc, or ...
     *              <Monthday List="N,N,N,Last"> 
     *                  <ExcludeWindow From="" To="" />
     *              </Monthday> 
     *           </Months>
     *         </Limits>
     * 
     * @param config
     */
    public void init(XElement config) {
        if (config != null) {
            String zone = config.getAttribute("TimeZone");

            if (StringUtil.isNotEmpty(zone))
                this.zone = DateTimeZone.forID(zone);

            String from = config.getAttribute("ValidFrom");

            if (!StringUtil.isEmpty(from)) {
                this.validFrom = TimeUtil.parseDateTime(from);

                // TODO not sure about this - parsing of ISO string should not be circumvented?
                if (this.zone != null)
                    this.validFrom = new DateTime(this.validFrom, this.zone);
            }

            String to = config.getAttribute("ValidTo");

            if (!StringUtil.isEmpty(to)) {
                this.validTo = TimeUtil.parseDateTime(to);

                // TODO not sure about this - parsing of ISO string should not be circumvented?
                if (this.zone != null)
                    this.validTo = new DateTime(this.validTo, this.zone);
            }

            // default to 10 years from now
            if (this.validTo == null)
                this.validTo = new DateTime().plusYears(10);

            for (XElement el : config.selectAll("Months")) {
                MonthWindow ww = new MonthWindow();
                ww.init(this, el);
                this.monthly.add(ww);
            }

            for (XElement el : config.selectAll("Weekdays")) {
                WeekdayWindow ww = new WeekdayWindow();
                ww.init(this, el);
                this.weekly.add(ww);
            }

            this.dailyWindow.init(config);

            String batch = config.getAttribute("LinkBatch");

            if (StringUtil.isNotEmpty(batch))
                this.parent = Hub.instance.getScheduler().getBatch(batch);
        } else {
            this.dailyWindow.init(null);
        }

        if (this.parent != null) {
            // if parent (batch) then we only use our daily overrides, 
            // not our month or weekly  
            this.dailyWindow.setParent(this.parent.getDailyWindow());
        }
    }

    // return true if instant can run within a window
    // return false if instant is in the past
    public boolean checkForRun(DateTime v) {
        if (this.zone != null)
            v = new DateTime(v, this.zone);

        // if this time was ended at mid or before midnight then entire day is blocked
        if (this.isEnded(v))
            return false;

        // if this time was not started by the end of the then entire day is blocked
        if (!this.isStarted(v))
            return false;

        // runs must also be now (recent) or future
        if (v.plusMinutes(5).isBeforeNow())
            return false;

        if (this.parent != null)
            return this.parent.checkForRun(v);

        CheckInfo ci = new CheckInfo();
        ci.setWhen(v);

        // if there are any months, those take precedence over other
        if (this.monthly.size() > 0) {
            for (MonthWindow ww : this.monthly)
                if (ww.appliesTo(ci))
                    return (ww.check(ci) == CheckLimitResult.Pass);

            return false;
        }
        // if there are any weeks, those take precedence over daily
        else if (this.weekly.size() > 0) {
            for (WeekdayWindow ww : this.weekly)
                if (ww.appliesTo(ci))
                    return (ww.check(ci) == CheckLimitResult.Pass);

            return false;
        }

        return (this.dailyWindow.check(v) == CheckLimitResult.Pass);
    }

    // return the start of the next available window for running
    // from "now"
    public DateTime nextAllowedRun() {
        return this.nextAllowedRunAfter(new DateTime().minusMinutes(1));
    }

    // return the start of the next available window for running (must always be after or equal to now)
    public DateTime nextAllowedRunAfter(DateTime lin) {
        if (this.zone != null)
            lin = new DateTime(lin, this.zone);

        // cannot run before now - 2 minutes
        if (lin.plusMinutes(5).isBeforeNow())
            lin = new DateTime().minusMinutes(1); // start back one minute so we can start on time

        // cannot run again
        if (this.isEnded(lin))
            return null;

        // must start at least at "from"
        if (!this.isStarted(lin))
            lin = this.validFrom;

        if (this.parent != null)
            return this.parent.nextAllowedRunAfter(lin);

        CheckInfo ci = new CheckInfo();
        ci.setWhen(lin);

        LocalTime nt = null;

        // move forward 1 day at a time till we find a date that has an opening
        while (true) {
            // if there are any months, those take precedence over other
            if (this.monthly.size() > 0) {
                for (MonthWindow ww : this.monthly)
                    if (ww.appliesTo(ci)) {
                        nt = ww.nextTimeOn(ci);
                        break;
                    }
            }
            // if there are any weeks, those take precedence over daily
            else if (this.weekly.size() > 0) {
                for (WeekdayWindow ww : this.weekly)
                    if (ww.appliesTo(ci)) {
                        nt = ww.nextTimeOn(ci);
                        break;
                    }
            } else
                nt = this.dailyWindow.nextTimeOn(ci.getWhen());

            if (nt != null)
                break;

            ci.incrementDay();

            // there is no next allowed
            if (this.isEnded(ci.getWhen()))
                return null;
        }

        lin = TimeUtil.withTime(ci.getWhen(), nt);

        // there is no next allowed
        if (this.isEnded(lin))
            return null;

        return lin;
    }

    public boolean isDateBlocked(DateTime tlast) {
        if (this.zone != null)
            tlast = new DateTime(tlast, this.zone);

        // if this time was ended at mid or before midnight then entire day is blocked
        if (this.isEnded(tlast.withTime(0, 0, 0, 0)))
            return true;

        // if this time was not started by the end of the then entire day is blocked
        if (!this.isStarted(tlast.withTime(23, 59, 59, 0)))
            return true;

        if (this.parent != null)
            return this.parent.isDateBlocked(tlast);

        CheckInfo ci = new CheckInfo();
        ci.setWhen(tlast);

        // if there are any months, those take precedence over other
        if (this.monthly.size() > 0) {
            for (MonthWindow ww : this.monthly)
                if (ww.appliesTo(ci))
                    return false;

            return true;
        }
        // if there are any weeks, those take precedence over daily      
        else if (this.weekly.size() > 0) {
            // only need to find one window to return false
            for (WeekdayWindow ww : this.weekly)
                if (ww.appliesTo(ci))
                    return false;

            return true;
        }

        return this.dailyWindow.excludeAll();
    }

    // return true if "now" is after valid start date 
    public boolean isStarted() {
        if (this.validFrom != null)
            return !this.validFrom.isAfterNow(); // now is equal or greater than from

        return true;
    }

    // return true if param is after valid start date 
    public boolean isStarted(DateTime scheduleDate) {
        if (this.validFrom != null)
            return !this.validFrom.isAfter(scheduleDate); // now is equal or greater than from

        return true;
    }

    // return true if "now" is after valid end date 
    public boolean isEnded() {
        if (this.validTo != null)
            return !this.validTo.isAfterNow(); // must be before now (not equal)

        return false;
    }

    // return true if param is after valid end date
    public boolean isEnded(DateTime scheduleDate) {
        if (this.validTo != null)
            return !this.validTo.isAfter(scheduleDate);

        return false;
    }

    // return the first valid date for this schedule
    public DateTime getFirstDate() {
        return this.validFrom;
    }

    // return the last valid date for this schedule
    public DateTime getLastDate() {
        return this.validTo;
    }
}