// Copyright (c) 2001 Dustin Sallings <dustin@spy.net> package net.spy.cron; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import net.spy.util.CloseUtil; import net.spy.util.SpyUtil; /** * Get a job queue as defined in a file. The file will be in one the following * formats: * * <p> * * <ul> * <li>YYYYMMDD-HHMMSS calfield calincrement classname args ...</li> * <li>HHMMSS calfield calincrement classname args ...</li> * <li>MMSS calfield calincrement classname args ...</li> * </ul> * * There are also special times that may be provided for less specific start * times: * * <ul> * <li>NOW - start immediately (or close to it) after startup, cycle from * that time</li> * <li>NEXT - similar to NOW, but skip the current startup. Each cycle * will be based on the startup time, but the first run will happen after * the delay finishes.</li> * </ul> * * In a case where fields are not provided, the current time will be * substituted. * * <p> * * For example: * * <pre> * 20010403-090000 DAY 1 net.spy.pagermusic.RunSubs * </pre> */ public class FileJobQueue extends JobQueue<MainJob> { private ClassLoader classLoader; /** * Get a new FileJobQueue based on what's in the given file. * @param cl the class loader * @param f the file to read * @throws IOException if there's a problem reading the jobs */ public FileJobQueue(ClassLoader cl, File f) throws IOException { super(); classLoader=cl; FileReader fr=null; try { fr=new FileReader(f); initQueue(fr); } finally { CloseUtil.close(fr); } } /** * Get a FileJobQueue from a file using the current classloader. * @param f the file */ public FileJobQueue(File f) throws IOException { this(null, f); classLoader=getClass().getClassLoader(); } /** * Get a new FileJobQueue from a Reader. * * @param cl the classloader * @param r the reader * @throws IOException if there's a problem reading the jobs */ public FileJobQueue(ClassLoader cl, Reader r) throws IOException { super(); classLoader=cl; initQueue(r); } /** * Get a FileJobQueue from a reader using the current classloader. * @param r the reader */ public FileJobQueue(Reader r) throws IOException { this(null, r); classLoader = getClass().getClassLoader(); } // Init the job queue. private void initQueue(Reader r) throws IOException { LineNumberReader lnr=new LineNumberReader(r); String line=lnr.readLine(); while(line!=null) { try { MainJob j=parseJob(line, lnr.getLineNumber()); if(j!=null) { addJob(j); getLogger().info("Added job %s to start at %s", j, j.getStartTime()); } } catch(Exception e) { getLogger().warn("Error parsing line %s", lnr.getLineNumber(), e); } line=lnr.readLine(); } lnr.close(); } // Parse an individual line from the job file. private MainJob parseJob(String line, int lineNum) throws ParseException { MainJob rv=null; line=line.trim(); // Ignore comments. if(line.startsWith("#")) { return(null); } // Ignore empty lines. if(line.length() < 1) { return(null); } String[] stuff=SpyUtil.split(" ", line); String dateS=stuff[0]; String fieldS=stuff[1]; String incrS=stuff[2]; String classS=stuff[3]; String[] args=new String[stuff.length-4]; // If there were args, copy them in instead. if(stuff.length>4) { System.arraycopy(stuff, 4, args, 0, args.length); } Date startDate=parseStartDate(dateS); int cf=parseCalendarField(fieldS); if(cf>=0) { TimeIncrement ti=new TimeIncrement(); ti.setField(cf); ti.setIncrement(Integer.parseInt(incrS)); // Get the next start date using the given increment, otherwise // the job will run *right now*. startDate=ti.nextDate(startDate); rv=new MainJob(classLoader, classS, args, startDate, ti); } else { if(startDate.getTime() < System.currentTimeMillis()) { getLogger().warn("At job on line %d is in the past.", lineNum); } else { rv=new MainJob(classLoader, classS, args, startDate); } } return(rv); } // Parse the date private Date parseStartDate(String in) throws ParseException { Date rv=null; long now=System.currentTimeMillis(); // Special cases if(in.equals("NOW")) { // Schedule it for a minute from now, which should make it as // close to now as anyone should care about rv=new Date(now+60000); } else if(in.equals("NEXT")) { // Make sure it's in the past. rv=new Date(now-1); } else { // Flip through all of the known formats until we get a match for(TimeFormat tf : getFormats()) { SimpleDateFormat sdf=tf.getFormat(); try { Date d=sdf.parse(in); rv=copyDate(d, tf.getFields()); } catch(ParseException e) { // Nope, try the next } } } if(rv==null) { throw new ParseException("Could not parse date: " + in, 0); } return (rv); } // These are the supported formats, and the fields we should remember // from them. private Collection<TimeFormat> getFormats() { Collection<TimeFormat> rv=new ArrayList<TimeFormat>(); int[] f1={Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND}; rv.add(new TimeFormat("yyyyMMdd-HHmmss", f1)); int[] f2={Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND}; rv.add(new TimeFormat("HHmmss", f2)); int[] f3={Calendar.MINUTE, Calendar.SECOND}; rv.add(new TimeFormat("mmss", f3)); return (rv); } // Create a new Date using the current time, and then copy all of the // fields of the passed in date that are specified in the given fields // array. private Date copyDate(Date d, int fields[]) { Calendar oldc=Calendar.getInstance(); oldc.setTime(d); Calendar newc=Calendar.getInstance(); // First, zero out the milliseconds. newc.set(Calendar.MILLISECOND, 0); // Copy the defined fields. for(int i=0; i<fields.length; i++) { newc.set(fields[i], oldc.get(fields[i])); } // Return the new time return(newc.getTime()); } private int parseCalendarField(String fieldName) { int rv=-1; if(fieldName.equals("YEAR")) { rv=Calendar.YEAR; } else if(fieldName.equals("MONTH")) { rv=Calendar.MONTH; } else if(fieldName.equals("DAY")) { rv=Calendar.DAY_OF_MONTH; } else if(fieldName.equals("HOUR")) { rv=Calendar.HOUR; } else if(fieldName.equals("MINUTE")) { rv=Calendar.MINUTE; } else if(fieldName.equals("SECOND")) { rv=Calendar.SECOND; } else if(fieldName.equals("ONCE")) { // This is an ``at'' job rv=-1; } else { getLogger().warn(fieldName + " is not a valid Calendar field."); } return(rv); } static final class TimeFormat extends Object { private SimpleDateFormat format=null; private int[] fields=null; TimeFormat(String fmt, int flds[]) { super(); this.format=new SimpleDateFormat(fmt); this.format.setLenient(false); this.fields=flds; } /** * Get the format this thing is holding. */ SimpleDateFormat getFormat() { return(format); } /** * Get the java.util.Calendar fields that should be preserved. */ int[] getFields() { return(fields); } } }