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)));
}
}
}