Hi,

included is the code for a "CronSchedulable" you can use to run jobs at 
a specific time of day.
It's far from perfect but has helped me a lot so far.
Marc called me names at the Palma training for not giving this one to 
the jboss community.
(He hasn't seen the code so far :-)

Enjoy,

Michael.

--

"She won't live. But - then again - who does ?" -- Edward James Olmos in 
'Blade Runner'
package de.j4production.base.time;

import java.util.*;
import org.apache.log4j.*;

/**
 * A Schedulable to use from the JBoss Scheduler.
 * It uses a format similar to the well known cron-command
 * to specify the "exact" time to run.
 *
 * It is quite usefull to run cpu-intensive jobs in the
 * midnight hours.
 *
 * To use it write a subclass which implements the
 * performCron-method and instantiate it with an appropriate
 * pattern.
 *
 * There are some limitations:
 *
 * 1) The advanceDay and nextTime functions are quite complex.
 *    You have to trust them or come up with something simpler.
 *
 * 2) The resolution you can get depends on the intervall in
 *    which the JBoss Scheduler calls this Schedulable.
 *    But: If the Scheduler failed to call in time, this is
 *    detected, and the performCron-method is immediatelly
 *    called the next time.
 *
 * 3) Not all cron formats are supported right now (NO RANGES!)
 *
 * 4) It could be better to port the time calculation to the
 *    Scheduler itself, so you can adjust the sleep-period
 *    to the circumstances. But you would need a priority-queue
 *    data structure to handle multiple Schedulables and adjust
 *    the sleep time for the first one in an efficient way.
 *
 * This code is under the LGPL-license. See the COPYING-file
 * in the JBoss distribution.
 *
 * @author Michael Bartmann ([EMAIL PROTECTED])
 * @version 1.0
 * @copyright 4Production AG 2001 (www.4production.de)
 */

public abstract class CronSchedulable
{
    private static Category log = Category.getInstance(CronSchedulable.class);

    protected long lastRunMillis;
    protected long nextRunMillis;

    private int[] minutes;
    private int[] hours;
    private int[] daysOfMonth;
    private int[] months;
    private int[] daysOfWeek;

    /**
     * This constructor uses five separate Strings
     * for the match patterns.
     *
     * Their format is similar to the well known cron command.
     * See the main method below for an example.
     *
     * @param minutesPattern
     * @param hourPattern
     * @param dayPattern
     * @param monthPattern
     * @param weekdayPattern
     */
    public CronSchedulable(String minutesPattern,
                           String hourPattern,
                           String dayPattern,
                           String monthPattern,
                           String weekdayPattern)
    {
        minutes = string2IntArray(minutesPattern,0,0,59,"minute");
        hours = string2IntArray(hourPattern,0,0,23,"hour");
        daysOfMonth = string2IntArray(dayPattern,0,1,31,"dayOfMonth");
        months = string2IntArray(monthPattern,-1,0,11,"month");
        daysOfWeek = string2IntArray(weekdayPattern,0,1,7,"dayOfWeek");
        lastRunMillis = System.currentTimeMillis();
        nextRunMillis = nextTime(lastRunMillis);
        log.info(this+" started at: "+new Date(lastRunMillis)+", next run at approx.: 
"+new Date(nextRunMillis));
    }


    /**
     * helper function to parse the cron format
     * @param xa
     * @param min
     * @param max
     * @return
     */
    private static int[] string2IntArray(String s, int offs, int min, int max, String 
fieldName)
    {
        if ((s==null)||(s.equals(""))||(s.equals("*")))
        {
            return new int[0];
        }
        StringTokenizer t = new StringTokenizer(s,"|",false);
        int[] h = new int[t.countTokens()];
        for (int i=0; i<h.length; i++)
        {
            int x = Integer.parseInt(t.nextToken()) + offs;
            if ((x<min)||(x>max))
            {
                throw new IllegalArgumentException("parameter "+(x-offs)+" is out of 
range for field "+fieldName);
            }
            h[i] = x;
        }
        return h;
    }

    /**
     * helper function to parse the cron format
     * @param xa
     * @param min
     * @param max
     * @return
     */
    private static boolean matches(int x, int[]xa, int min, int max)
    {
        if (xa.length==0)
        {
            return true;
        }
        else
        {
            for (int i=0;i<xa.length;i++)
            {
                if (xa[i]==x)
                {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * helper function to parse the cron format
     * @param xa
     * @param min
     * @param max
     * @return
     */
    private static boolean hasMoreMatches(int x, int[]xa, int min, int max)
    {
        if (xa.length==0)
        {
            return x<max;
        }
        else
        {
            for (int i=0;i<xa.length;i++)
            {
                if (xa[i]>x)
                {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * helper function to parse the cron format
     * @param xa
     * @param min
     * @param max
     * @return
     */
    private static int nextMatching(int x, int[]xa, int min, int max)
    {
        if (xa.length==0)
        {
            return x+1;
        }
        else
        {
            for (int i=0;i<xa.length;i++)
            {
                if (xa[i]>x)
                {
                    return xa[i];
                }
            }
            return max; // should not happen!!!
        }
    }

    /**
     * helper function to parse the cron format
     * @param xa
     * @param min
     * @param max
     * @return
     */
    private static int first(int[]xa, int min, int max)
    {
        if (xa.length==0)
        {
            return min;
        }
        else
        {
            return xa[0];
        }
    }

    /**
     * Helper function to advance a calendar by one day.
     * There should be a simpler way...
     * @param gCal
     */
    private void advanceDay(GregorianCalendar gCal)
    {
        do
        {
            gCal.add(GregorianCalendar.DATE,1);
        } while (!(   
matches(gCal.get(GregorianCalendar.DAY_OF_MONTH),daysOfMonth,1,gCal.getActualMaximum(GregorianCalendar.DAY_OF_MONTH))
                      && matches(gCal.get(GregorianCalendar.MONTH),months,0,11)
                      && 
matches(gCal.get(GregorianCalendar.DAY_OF_WEEK),daysOfWeek,1,7)
                      ));
    }

    /**
     * Calculate the next time to run performCron, if we know that
     * it was last run at the given argument.
     * @param lastTime the last time performCron() was actually run.
     * @return the next time to run performCron()
     */
    private long nextTime(long lastTime)
    {
        GregorianCalendar gCal = new GregorianCalendar();
        gCal.setTime(new Date(lastTime));
        gCal.set(GregorianCalendar.SECOND,0);
        gCal.set(GregorianCalendar.MILLISECOND,0);
        if (    matches(gCal.get(GregorianCalendar.DAY_OF_WEEK),daysOfWeek,0,6)
                && matches(gCal.get(GregorianCalendar.MONTH),months,0,11)
                && matches(gCal.get(GregorianCalendar.DAY_OF_MONTH),daysOfMonth,0,31))
        {
            // ok, so this is a matching day.
            // if we find a next schedule for this day we're done,
            // but if this is the last schedule for this day, we must advance
            // to the next schedule on the next possible day.
            if (matches(gCal.get(GregorianCalendar.HOUR_OF_DAY),hours,0,23))
            {
                // everything matches up to the hour, we check the seconds
                if (hasMoreMatches(gCal.get(GregorianCalendar.MINUTE),minutes,0,59))
                {
                    
gCal.set(GregorianCalendar.MINUTE,nextMatching(gCal.get(GregorianCalendar.MINUTE),minutes,0,59));
                    // the rest of the fields remain intact
                }
                else
                {
                    // oops, no more minute this hour
                    gCal.set(GregorianCalendar.MINUTE,first(minutes,0,59));
                    // all other fields match, so we must advance hour
                    if 
(hasMoreMatches(gCal.get(GregorianCalendar.HOUR_OF_DAY),hours,0,23))
                    {
                        
gCal.set(GregorianCalendar.HOUR_OF_DAY,nextMatching(gCal.get(GregorianCalendar.HOUR_OF_DAY),hours,0,23));
                        // the rest of the fields remain intact
                    }
                    else
                    {
                        gCal.set(GregorianCalendar.HOUR_OF_DAY,first(hours,0,23));
                        // all other fields match, so we must advance date
                        advanceDay(gCal);
                    }
                }
            }
            else if 
(hasMoreMatches(gCal.get(GregorianCalendar.HOUR_OF_DAY),hours,0,23))
            {
                gCal.set(GregorianCalendar.MINUTE,first(minutes,0,59));
                
gCal.set(GregorianCalendar.HOUR_OF_DAY,nextMatching(gCal.get(GregorianCalendar.HOUR_OF_DAY),hours,0,23));
                // the rest of the fields remain intact
            }
            else
            {
                gCal.set(GregorianCalendar.MINUTE,first(minutes,0,59));
                gCal.set(GregorianCalendar.HOUR_OF_DAY,first(hours,0,23));
                advanceDay(gCal);
            }
        }
        else
        {
            gCal.set(GregorianCalendar.MINUTE,first(minutes,0,59));
            gCal.set(GregorianCalendar.HOUR_OF_DAY,first(hours,0,23));
            advanceDay(gCal);
        }
        return gCal.getTime().getTime();
    }

    /**
     * This method is called by the JBoss build-in-scheduler.
     * That scheduler must be configured to call this method as least as often
     * to allow it to run performCron.
     * @param pTimeOfCall
     * @param pRemainingRepetitions
     */
    public void perform(Date pTimeOfCall, long pRemainingRepetitions)
    {
        long now = System.currentTimeMillis();
        if (now>=nextRunMillis)
        {
            lastRunMillis = now;
            nextRunMillis = nextTime(lastRunMillis);
            log.info(this+" running at: "+new Date(lastRunMillis));
            performCron();
            log.info(this+" done, next run at approx.: "+new Date(nextRunMillis));
        }
    }

    /**
     * This method is called if the cron-arguments allow.
     * Put your code here, when extending this class to do something useful.
     */
    protected abstract void performCron();


//---------- SNIP HERE TO GET RID OF DEBUGGING/EXAMPLE CODE ---------------


    /**
     * This method is only used by the example code in main.
     */
    private void debug(long then)
    {
        long result = nextTime(then);
        log.debug("next after "+new Date(then)+" is: "+new Date(result));
    }

    /**
     * This main method constructs a simple CronSchedulable and calls
     * the internal nextTime()-method.
     * Normal debugging would last years :-)
     */
    public static void main(String args[])
    {
        CronSchedulable cron = new 
CronSchedulable("0|15|30|45","0|6|12|18","13","","6")
        {
            public void performCron()
            {
                log.debug("performCron " + new Date());
            }
        };
        Random r = new Random();
        long now = System.currentTimeMillis();
        long then = now;
        cron.debug(now);
        for (int i=0; i<1000; i++)
        {
            long millisPerSecond = 1000;
            long millisPerMinute = millisPerSecond * 60;
            long millisPerHour   = millisPerMinute * 60;
            long millisPerDay    = millisPerHour * 24;
            long millisPerYear   = millisPerDay * 366;
            cron.debug(now+(r.nextLong()%(millisPerYear*40)));
        }
    }

}

Reply via email to