All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.sauronsoftware.cron4j.CronParser Maven / Gradle / Ivy

The newest version!
/*
 * cron4j - A pure Java cron-like scheduler
 * 
 * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version
 * 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License 2.1 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License version 2.1 along with this program.
 * If not, see .
 */
package it.sauronsoftware.cron4j;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;

/**
 * 

* A parser for crontab-like formatted files and streams. *

*

* If you want to schedule a list of tasks declared in a crontab-like file you * don't need the CronParser, since you can do it by adding the file to the * scheduler, with the {@link Scheduler#scheduleFile(File)} method. *

*

* Consider to use the CronParser if the {@link Scheduler#scheduleFile(File)} * method is not enough for you. In example, you may need to fetch the task list * from a remote source which is not representable as a {@link File} object (a * document on a remote server, a DBMS result set and so on). To solve the * problem you can implement your own {@link TaskCollector}, getting the * advantage of the CronParser to parse easily any crontab-like content. *

*

* You can parse a whole file/stream, but you can also parse a single line. *

*

* A line can be empty, can contain a comment or it can be a scheduling line. *

*

* A line containing no characters or a line with only space characters is * considered an empty line. *

*

* A line whose first non-space character is a number sign (#) is considered a * comment. *

*

* Empty lines and comment lines are ignored by the parser. *

*

* Any other kind of line is parsed as a scheduling line. *

*

* A valid scheduling line respects the following structure: *

* *
 * scheduling-pattern [options] command [args]
 * 
* *
    *
  • scheduling-pattern is a valid scheduling pattern, according with * the definition given by the {@link SchedulingPattern} class.
  • *
  • options is a list of optional informations used by cron4j to * prepare the task execution environment. See below for a more detailed * description.
  • *
  • command is a system valid command, such an executable call.
  • *
  • args is a list of optional arguments for the command.
  • *
*

* After the scheduling pattern item, other tokens in each line are space * separated or delimited with double quotation marks ("). *

*

* Double quotation marks delimited items can take advantage of the following * escape sequences: *

*
    *
  • \" - quotation mark
  • *
  • \\ - back slash
  • *
  • \/ - slash
  • *
  • \b - back space
  • *
  • \f - form feed
  • *
  • \n - new line
  • *
  • \r - carriage return
  • *
  • \t - horizontal tab
  • *
  • \u005c\u0075four-hex-digits - the character at the given unicode * index
  • *
*

* The options token collection can include one or more of the * following elements: *

*
    *
  • IN:file-path - Redirects the command standard input channel to * the specified file.
  • *
  • OUT:file-path - Redirects the command standard output channel to * the specified file.
  • *
  • ERR:file-path - Redirects the command standard error channel to * the specified file.
  • *
  • ENV:name=value - Defines an environment variable in the * scope of the command.
  • *
  • DIR:directory-path - Sets the path of the working directory for * the command. This feature is not supported if the executing JVM is less than * 1.3.
  • *
*

* It is also possible to schedule the invocation of a method of a Java class in * the scope of the parser ClassLoader. The method has to be static and it must * accept an array of strings as its sole argument. To invoke a method of this * kind the syntax is: *

* *
 * scheduling-pattern java:className#methodName [args]
 * 
*

* The #methodName part can be omitted: in this case the * main(String[]) method will be assumed. *

*

* Please note that static methods are invoked within the scheduler same JVM, * without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options * can't be applied. *

*

* Invalid scheduling lines are discarded without blocking the parsing * procedure, but an error message is printed in the application standard error * channel. *

*

* Valid examples: *

* *
 * 0 5 * * * sol.exe
 * 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55
 * 0,30 4 * * * "OUT:C:\Documents and Settings\Carlo\ping.txt" ping 10.9.43.55
 * 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat "Nightly Build"
 * 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2
 * 
* * @author Carlo Pelliccia * @since 2.0 */ public class CronParser { /** * Instantiation prohibited. */ private CronParser() { } /** *

* Builds a task list reading it from a file. *

* *

* The file is treated as UTF-8. If your source file is not UTF-8 encoded * establish by yourself a {@link Reader} using the right charset and pass * it to the {@link CronParser#parse(Reader)} method. *

* *

* Syntax and semantics errors in the source file are not blocking. Invalid * lines are discarded, and they cause just a stack trace to be printed in * the standard error channel as a notification. *

* * @param file * The file. * @return The task table parsed from the file. * @throws IOException * I/O error. */ public static TaskTable parse(File file) throws IOException { InputStream stream = null; try { stream = new FileInputStream(file); return parse(stream); } finally { if (stream != null) { try { stream.close(); } catch (Throwable t) { ; } } } } /** *

* Builds a task list reading it from an URL. *

* *

* Contents fetched from the URL are treated as UTF-8. If your source is not * UTF-8 encoded establish by yourself a {@link Reader} using the right * charset and pass it to the {@link CronParser#parse(Reader)} method. *

* *

* Syntax and semantics errors in the retrieved document are not blocking. * Invalid lines are discarded, and they cause just a stack trace to be * printed in the standard error channel as a notification. *

* * @param url * The URL. * @return The task table parsed from the contents fetched from the given * URL. * @throws IOException * I/O error. */ public static TaskTable parse(URL url) throws IOException { InputStream stream = null; try { stream = url.openStream(); return parse(stream); } finally { if (stream != null) { try { stream.close(); } catch (Throwable t) { ; } } } } /** *

* Builds a task list reading it from an input stream. *

* *

* The stream is treated as UTF-8. If your source is not UTF-8 encoded * establish by yourself a {@link Reader} using the right charset and pass * it to the {@link CronParser#parse(Reader)} method. *

* *

* Syntax and semantics errors in the source stream are not blocking. * Invalid lines are discarded, and they cause just a stack trace to be * printed in the standard error channel as a notification. *

* * @param stream * The input stream. * @return The task table parsed from the stream contents. * @throws IOException * I/O error. */ public static TaskTable parse(InputStream stream) throws IOException { return parse(new InputStreamReader(stream, "UTF-8")); } /** *

* Builds a task list reading it from a reader. *

* *

* Syntax and semantics errors in the source reader are not blocking. * Invalid lines are discarded, and they cause just a stack trace to be * printed in the standard error channel as a notification. *

* * @param reader * The reader. * @return The task table parsed from the contents in the reader. * @throws IOException * I/O error. */ public static TaskTable parse(Reader reader) throws IOException { TaskTable table = new TaskTable(); BufferedReader bufferedReader = new BufferedReader(reader); try { String line; while ((line = bufferedReader.readLine()) != null) { try { parseLine(table, line); } catch (Exception e) { e.printStackTrace(); continue; } } } finally { reader.close(); } return table; } /** * Parses a crontab-like line. * * @param table * The table on which the parsed task will be stored, by * side-effect. * @param line * The crontab-like line. * @throws Exception * The supplied line doesn't represent a valid task line. */ public static void parseLine(TaskTable table, String line) throws Exception { line = line.trim(); if (line.length() == 0 || line.charAt(0) == '#') { return; } // Detecting the pattern. int size = line.length(); String pattern = null; for (int i = size; i >= 0; i--) { String aux = line.substring(0, i); if (SchedulingPattern.validate(aux)) { pattern = aux; break; } } if (pattern == null) { throw new Exception("Invalid cron line: " + line); } line = line.substring(pattern.length()); size = line.length(); // Splitting the line ArrayList splitted = new ArrayList(); StringBuffer current = null; boolean quotes = false; for (int i = 0; i < size; i++) { char c = line.charAt(i); if (current == null) { if (c == '"') { current = new StringBuffer(); quotes = true; } else if (c > ' ') { current = new StringBuffer(); current.append(c); quotes = false; } } else { boolean closeCurrent; if (quotes) { closeCurrent = (c == '"'); } else { closeCurrent = (c <= ' '); } if (closeCurrent) { if (current != null && current.length() > 0) { String str = current.toString(); if (quotes) { str = escape(str); } splitted.add(str); } current = null; } else { current.append(c); } } } if (current != null && current.length() > 0) { String str = current.toString(); if (quotes) { str = escape(str); } splitted.add(str); current = null; } // Analyzing size = splitted.size(); int status = 0; // Status values: // 0 -> fetching environment variables, working directory and channels // 1 -> fetching the command and its arguments String dirString = null; File stdinFile = null; File stdoutFile = null; File stderrFile = null; ArrayList envsList = new ArrayList(); String command = null; ArrayList argsList = new ArrayList(); for (int i = 0; i < size; i++) { String tk = (String) splitted.get(i); // Check the local status. if (status == 0) { // Environment variables, working directory and channels if (tk.startsWith("ENV:")) { envsList.add(tk.substring(4)); continue; } else if (tk.startsWith("DIR:")) { dirString = tk.substring(4); continue; } else if (tk.startsWith("IN:")) { stdinFile = new File(tk.substring(3)); continue; } else if (tk.startsWith("OUT:")) { stdoutFile = new File(tk.substring(4)); continue; } else if (tk.startsWith("ERR:")) { stderrFile = new File(tk.substring(4)); continue; } else { status = 1; } } if (status == 1) { // Command or argument? if (command == null) { command = tk; } else { argsList.add(tk); } } } // Task preparing. Task task; // Command evaluation. if (command == null) { // No command! throw new Exception("Invalid cron line: " + line); } else if (command.startsWith("java:")) { // Java inner-process. String className = command.substring(5); if (className.length() == 0) { throw new Exception("Invalid Java class name on line: " + line); } String methodName; int sep = className.indexOf('#'); if (sep == -1) { methodName = "main"; } else { methodName = className.substring(sep + 1); className = className.substring(0, sep); if (methodName.length() == 0) { throw new Exception("Invalid Java method name on line: " + line); } } String[] args = new String[argsList.size()]; for (int i = 0; i < argsList.size(); i++) { args[i] = (String) argsList.get(i); } task = new StaticMethodTask(className, methodName, args); } else { // External command. String[] cmdarray = new String[1 + argsList.size()]; cmdarray[0] = command; for (int i = 0; i < argsList.size(); i++) { cmdarray[i + 1] = (String) argsList.get(i); } // Environments. String[] envs = null; size = envsList.size(); if (size > 0) { envs = new String[size]; for (int i = 0; i < size; i++) { envs[i] = (String) envsList.get(i); } } // Working directory. File dir = null; if (dirString != null) { dir = new File(dirString); if (!dir.exists() || !dir.isDirectory()) { throw new Exception( "Invalid cron working directory parameter at line: " + line, new FileNotFoundException(dirString + " doesn't exist or it is not a directory")); } } // Builds the task. ProcessTask process = new ProcessTask(cmdarray, envs, dir); // Channels. if (stdinFile != null) { process.setStdinFile(stdinFile); } if (stdoutFile != null) { process.setStdoutFile(stdoutFile); } if (stderrFile != null) { process.setStderrFile(stderrFile); } task = process; } // End. table.add(new SchedulingPattern(pattern), task); } /** * Escapes special chars occurrences. * * @param str * The input stream. * @return The decoded output stream. */ private static String escape(String str) { int size = str.length(); StringBuffer b = new StringBuffer(); for (int i = 0; i < size; i++) { int skip = 0; char c = str.charAt(i); if (c == '\\') { if (i < size - 1) { char d = str.charAt(i + 1); if (d == '"') { b.append('"'); skip = 2; } else if (d == '\\') { b.append('\\'); skip = 2; } else if (d == '/') { b.append('/'); skip = 2; } else if (d == 'b') { b.append('\b'); skip = 2; } else if (d == 'f') { b.append('\f'); skip = 2; } else if (d == 'n') { b.append('\n'); skip = 2; } else if (d == 'r') { b.append('\r'); skip = 2; } else if (d == 't') { b.append('\t'); skip = 2; } else if (d == 'u') { if (i < size - 5) { String hex = str.substring(i + 2, i + 6); try { int code = Integer.parseInt(hex, 16); if (code >= 0) { b.append((char) code); skip = 6; } } catch (NumberFormatException e) { ; } } } } } if (skip == 0) { b.append(c); } else { i += (skip - 1); } } return b.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy