+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
index 338ab43..3cb89ad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -29,7 +29,7 @@
public XxlJobInfo loadById(@Param("id") int id);
- public int update(XxlJobInfo item);
+ public int update(XxlJobInfo xxlJobInfo);
public int delete(@Param("id") int id);
@@ -37,4 +37,9 @@
public int findAllCount();
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
index 338ab43..3cb89ad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -29,7 +29,7 @@
public XxlJobInfo loadById(@Param("id") int id);
- public int update(XxlJobInfo item);
+ public int update(XxlJobInfo xxlJobInfo);
public int delete(@Param("id") int id);
@@ -37,4 +37,9 @@
public int findAllCount();
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
index fbcc360..e8ef432 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -28,7 +28,7 @@
public Map pageList(int start, int length, int jobGroup, String jobDesc, String executorHandler, String filterTime);
/**
- * add job, default quartz stop
+ * add job
*
* @param jobInfo
* @return
@@ -36,7 +36,7 @@
public ReturnT add(XxlJobInfo jobInfo);
/**
- * update job, update quartz-cron if started
+ * update job
*
* @param jobInfo
* @return
@@ -44,15 +44,15 @@
public ReturnT update(XxlJobInfo jobInfo);
/**
- * remove job, unbind quartz
- *
+ * remove job
+ * *
* @param id
* @return
*/
public ReturnT remove(int id);
/**
- * start job, bind quartz
+ * start job
*
* @param id
* @return
@@ -60,7 +60,7 @@
public ReturnT start(int id);
/**
- * stop job, unbind quartz
+ * stop job
*
* @param id
* @return
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
index 338ab43..3cb89ad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -29,7 +29,7 @@
public XxlJobInfo loadById(@Param("id") int id);
- public int update(XxlJobInfo item);
+ public int update(XxlJobInfo xxlJobInfo);
public int delete(@Param("id") int id);
@@ -37,4 +37,9 @@
public int findAllCount();
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
index fbcc360..e8ef432 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -28,7 +28,7 @@
public Map pageList(int start, int length, int jobGroup, String jobDesc, String executorHandler, String filterTime);
/**
- * add job, default quartz stop
+ * add job
*
* @param jobInfo
* @return
@@ -36,7 +36,7 @@
public ReturnT add(XxlJobInfo jobInfo);
/**
- * update job, update quartz-cron if started
+ * update job
*
* @param jobInfo
* @return
@@ -44,15 +44,15 @@
public ReturnT update(XxlJobInfo jobInfo);
/**
- * remove job, unbind quartz
- *
+ * remove job
+ * *
* @param id
* @return
*/
public ReturnT remove(int id);
/**
- * start job, bind quartz
+ * start job
*
* @param id
* @return
@@ -60,7 +60,7 @@
public ReturnT start(int id);
/**
- * stop job, unbind quartz
+ * stop job
*
* @param id
* @return
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
index 0edab35..ba40bd8 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -2,8 +2,8 @@
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -14,14 +14,13 @@
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
-import org.quartz.CronExpression;
-import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
+import java.text.ParseException;
import java.util.*;
/**
@@ -48,13 +47,6 @@
List list = xxlJobInfoDao.pageList(start, length, jobGroup, jobDesc, executorHandler);
int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, jobDesc, executorHandler);
- // fill job info
- if (list!=null && list.size()>0) {
- for (XxlJobInfo jobInfo : list) {
- XxlJobDynamicScheduler.fillJobInfo(jobInfo);
- }
- }
-
// package result
Map maps = new HashMap();
maps.put("recordsTotal", list_count); // 总记录数
@@ -197,6 +189,15 @@
return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
}
+ // next trigger time
+ long nextTriggerTime = 0;
+ try {
+ nextTriggerTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+ }
+
exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
exists_jobInfo.setJobCron(jobInfo.getJobCron());
exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
@@ -209,18 +210,10 @@
exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
+ exists_jobInfo.setTriggerNextTime(nextTriggerTime);
xxlJobInfoDao.update(exists_jobInfo);
- // update quartz-cron if started
- try {
- String qz_name = String.valueOf(exists_jobInfo.getId());
- XxlJobDynamicScheduler.updateJobCron(qz_name, exists_jobInfo.getJobCron());
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
return ReturnT.SUCCESS;
}
@@ -230,76 +223,54 @@
if (xxlJobInfo == null) {
return ReturnT.SUCCESS;
}
- String name = String.valueOf(xxlJobInfo.getId());
- try {
- // unbind quartz
- XxlJobDynamicScheduler.removeJob(name);
-
- xxlJobInfoDao.delete(id);
- xxlJobLogDao.delete(id);
- xxlJobLogGlueDao.deleteByJobId(id);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
+ xxlJobInfoDao.delete(id);
+ xxlJobLogDao.delete(id);
+ xxlJobLogGlueDao.deleteByJobId(id);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT start(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
- String cronExpression = xxlJobInfo.getJobCron();
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
+
+ xxlJobInfo.setTriggerStatus(1);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
+
+ xxlJobInfoDao.update(xxlJobInfo);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT stop(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- // bind quartz
- boolean ret = XxlJobDynamicScheduler.removeJob(name);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
- }
- /*@Override
- public ReturnT triggerJob(int id, int failRetryCount) {
+ xxlJobInfo.setTriggerStatus(0);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
- JobTriggerPoolHelper.trigger(id, failRetryCount);
+ xxlJobInfoDao.update(xxlJobInfo);
return ReturnT.SUCCESS;
-
- *//*XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- if (xxlJobInfo == null) {
- return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_unvalid")) );
- }
-
- String group = String.valueOf(xxlJobInfo.getJobGroup());
- String name = String.valueOf(xxlJobInfo.getId());
-
- try {
- XxlJobDynamicScheduler.triggerJob(name, group);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return new ReturnT(ReturnT.FAIL_CODE, e.getMessage());
- }*//*
-
- }*/
+ }
@Override
public Map dashboardInfo() {
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
index 338ab43..3cb89ad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -29,7 +29,7 @@
public XxlJobInfo loadById(@Param("id") int id);
- public int update(XxlJobInfo item);
+ public int update(XxlJobInfo xxlJobInfo);
public int delete(@Param("id") int id);
@@ -37,4 +37,9 @@
public int findAllCount();
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
index fbcc360..e8ef432 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -28,7 +28,7 @@
public Map pageList(int start, int length, int jobGroup, String jobDesc, String executorHandler, String filterTime);
/**
- * add job, default quartz stop
+ * add job
*
* @param jobInfo
* @return
@@ -36,7 +36,7 @@
public ReturnT add(XxlJobInfo jobInfo);
/**
- * update job, update quartz-cron if started
+ * update job
*
* @param jobInfo
* @return
@@ -44,15 +44,15 @@
public ReturnT update(XxlJobInfo jobInfo);
/**
- * remove job, unbind quartz
- *
+ * remove job
+ * *
* @param id
* @return
*/
public ReturnT remove(int id);
/**
- * start job, bind quartz
+ * start job
*
* @param id
* @return
@@ -60,7 +60,7 @@
public ReturnT start(int id);
/**
- * stop job, unbind quartz
+ * stop job
*
* @param id
* @return
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
index 0edab35..ba40bd8 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -2,8 +2,8 @@
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -14,14 +14,13 @@
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
-import org.quartz.CronExpression;
-import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
+import java.text.ParseException;
import java.util.*;
/**
@@ -48,13 +47,6 @@
List list = xxlJobInfoDao.pageList(start, length, jobGroup, jobDesc, executorHandler);
int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, jobDesc, executorHandler);
- // fill job info
- if (list!=null && list.size()>0) {
- for (XxlJobInfo jobInfo : list) {
- XxlJobDynamicScheduler.fillJobInfo(jobInfo);
- }
- }
-
// package result
Map maps = new HashMap();
maps.put("recordsTotal", list_count); // 总记录数
@@ -197,6 +189,15 @@
return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
}
+ // next trigger time
+ long nextTriggerTime = 0;
+ try {
+ nextTriggerTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+ }
+
exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
exists_jobInfo.setJobCron(jobInfo.getJobCron());
exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
@@ -209,18 +210,10 @@
exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
+ exists_jobInfo.setTriggerNextTime(nextTriggerTime);
xxlJobInfoDao.update(exists_jobInfo);
- // update quartz-cron if started
- try {
- String qz_name = String.valueOf(exists_jobInfo.getId());
- XxlJobDynamicScheduler.updateJobCron(qz_name, exists_jobInfo.getJobCron());
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
return ReturnT.SUCCESS;
}
@@ -230,76 +223,54 @@
if (xxlJobInfo == null) {
return ReturnT.SUCCESS;
}
- String name = String.valueOf(xxlJobInfo.getId());
- try {
- // unbind quartz
- XxlJobDynamicScheduler.removeJob(name);
-
- xxlJobInfoDao.delete(id);
- xxlJobLogDao.delete(id);
- xxlJobLogGlueDao.deleteByJobId(id);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
+ xxlJobInfoDao.delete(id);
+ xxlJobLogDao.delete(id);
+ xxlJobLogGlueDao.deleteByJobId(id);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT start(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
- String cronExpression = xxlJobInfo.getJobCron();
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
+
+ xxlJobInfo.setTriggerStatus(1);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
+
+ xxlJobInfoDao.update(xxlJobInfo);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT stop(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- // bind quartz
- boolean ret = XxlJobDynamicScheduler.removeJob(name);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
- }
- /*@Override
- public ReturnT triggerJob(int id, int failRetryCount) {
+ xxlJobInfo.setTriggerStatus(0);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
- JobTriggerPoolHelper.trigger(id, failRetryCount);
+ xxlJobInfoDao.update(xxlJobInfo);
return ReturnT.SUCCESS;
-
- *//*XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- if (xxlJobInfo == null) {
- return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_unvalid")) );
- }
-
- String group = String.valueOf(xxlJobInfo.getJobGroup());
- String name = String.valueOf(xxlJobInfo.getId());
-
- try {
- XxlJobDynamicScheduler.triggerJob(name, group);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return new ReturnT(ReturnT.FAIL_CODE, e.getMessage());
- }*//*
-
- }*/
+ }
@Override
public Map dashboardInfo() {
diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
index 78881a3..f8ff78e 100644
--- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
+++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
@@ -23,24 +23,24 @@
- INSERT INTO XXL_JOB_QRTZ_TRIGGER_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
+ INSERT INTO XXL_JOB_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
values ( #{appName}, #{title}, #{order}, #{addressType}, #{addressList});
- UPDATE XXL_JOB_QRTZ_TRIGGER_GROUP
+ UPDATE XXL_JOB_GROUP
SET `app_name` = #{appName},
`title` = #{title},
`order` = #{order},
@@ -50,13 +50,13 @@
- DELETE FROM XXL_JOB_QRTZ_TRIGGER_GROUP
+ DELETE FROM XXL_JOB_GROUP
WHERE id = #{id}
diff --git a/README.md b/README.md
index f7ae073..fff6cf8 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@
## Features
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
diff --git "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md" "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
index 5b544d7..35da6ac 100644
--- "a/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
+++ "b/doc/XXL-JOB\345\256\230\346\226\271\346\226\207\346\241\243.md"
@@ -20,7 +20,7 @@
### 1.3 特性
- 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
- 2、动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
-- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;
+- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA;
- 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
@@ -776,18 +776,16 @@
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)
### 5.2 “调度数据库”配置
-XXL-JOB调度模块基于Quartz集群实现,其“调度数据库”是在Quartz的11张集群mysql表基础上扩展而成。
+XXL-JOB调度模块基于自研调度组件并支持集群部署,调度数据库表说明如下:
-XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。
+ - XXL_JOB_LOCK:任务调度锁表;
+ - XXL_JOB_GROUP:执行器信息表,维护任务执行器信息;
+ - XXL_JOB_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
+ - XXL_JOB_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
+ - XXL_JOB_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
+ - XXL_JOB_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
+ - XXL_JOB_USER:系统用户表;
-然后,在此基础上新增了几张张扩展表,如下:
- - XXL_JOB_QRTZ_TRIGGER_GROUP:执行器信息表,维护任务执行器信息;
- - XXL_JOB_QRTZ_TRIGGER_REGISTRY:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- - XXL_JOB_QRTZ_TRIGGER_INFO:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- - XXL_JOB_QRTZ_TRIGGER_LOG:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- - XXL_JOB_QRTZ_TRIGGER_LOGGLUE:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
-因此,XXL-JOB调度数据库共计用于16张数据库表。
### 5.3 架构设计
#### 5.3.1 设计思想
@@ -820,58 +818,30 @@
XXL-JOB弥补了quartz的上述不足之处。
-#### 5.4.2 RemoteHttpJobBean
-常规Quartz的开发,任务逻辑一般维护在QuartzJobBean中,耦合很严重。XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块中的所有调度任务使用同一个QuartzJobBean,即RemoteHttpJobBean。不同的调度任务将各自参数维护在各自扩展表数据中,当触发RemoteHttpJobBean执行时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。
+#### 5.4.2 自研调度模块
+XXL-JOB最终选择自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
-这种调用模型类似RPC调用,RemoteHttpJobBean提供调用代理的功能,而执行器提供远程服务的功能。
+XXL-JOB中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能。
#### 5.4.3 调度中心HA(集群)
-基于Quartz的集群方案,数据库选用Mysql;集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
-
-```
-# for cluster
-org.quartz.jobStore.tablePrefix = XXL_JOB_QRTZ_
-org.quartz.scheduler.instanceId: AUTO
-org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
-org.quartz.jobStore.isClustered: true
-org.quartz.jobStore.clusterCheckinInterval: 1000
-```
+基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。
#### 5.4.4 调度线程池
调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。
-```
-org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
-org.quartz.threadPool.threadCount: 50
-org.quartz.threadPool.threadPriority: 5
-org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
-```
-#### 5.4.5 @DisallowConcurrentExecution
-XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
+#### 5.4.5 并行调度
+XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。
XXL-JOB的每个调度任务虽然在调度模块是并行调度执行的,但是任务调度传递到任务模块的“执行器”确实串行执行的,同时支持任务终止。
-#### 5.4.6 misfire
-错过了触发时间,处理规则。
-可能原因:服务重启;调度线程被QuartzJobBean阻塞,线程被耗尽;某个任务启用了@DisallowConcurrentExecution,上次调度持续阻塞,下次调度被错过;
+#### 5.4.6 过期处理策略
+任务调度错过了触发时间:
+- 可能原因:服务重启;调度线程被阻塞,线程被耗尽;上次调度持续阻塞,下次调度被错过;
+- 处理策略:
+ - 过期5s内:立即触发一次,并计算下次触发时间;
+ - 过期超过5s:忽略过期触发,计算下次触发时间;
-quartz.properties中关于misfire的阀值配置如下,单位毫秒:
-```
-org.quartz.jobStore.misfireThreshold: 60000
-```
-
-Misfire规则:
- withMisfireHandlingInstructionDoNothing:不触发立即执行,等待下次调度;
- withMisfireHandlingInstructionIgnoreMisfires:以错过的第一个频率时间立刻开始执行;
- withMisfireHandlingInstructionFireAndProceed:以当前时间为触发频率立刻触发一次执行;
-
-XXL-JOB默认misfire规则为:withMisfireHandlingInstructionDoNothing
-
-```
-CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getJobCron()).withMisfireHandlingInstructionDoNothing();
-CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-```
#### 5.4.7 日志回调服务
调度模块的“调度中心”作为Web服务部署时,一方面承担调度中心功能,另一方面也为执行器提供API服务。
@@ -926,7 +896,7 @@
#### 5.4.11 全异步化 & 轻量级
-- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用时间;
+- 全异步化设计:XXL-JOB系统中业务逻辑在远程执行器执行,触发流程全异步化设计。相比直接在调度中心内部执行业务逻辑,极大的降低了调度线程占用时间;
- 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
- 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
- 轻量级设计:XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;
@@ -988,7 +958,7 @@
自v1.5版本之后, 任务取消了"任务执行机器"属性, 改为通过任务注册和自动发现的方式, 动态获取远程执行器地址并执行。
AppName: 每个执行器机器集群的唯一标示, 任务注册以 "执行器" 为最小粒度进行注册; 每个任务通过其绑定的执行器可感知对应的执行器机器列表;
- 注册表: 见"XXL_JOB_QRTZ_TRIGGER_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
+ 注册表: 见"XXL_JOB_REGISTRY"表, "执行器" 在进行任务注册时将会周期性维护一条注册记录,即机器地址和AppName的绑定关系; "调度中心" 从而可以动态感知每个AppName在线的机器列表;
执行器注册: 任务注册Beat周期默认30s; 执行器以一倍Beat进行执行器注册, 调度中心以一倍Beat进行动态任务发现; 注册信息的失效时间被三倍Beat;
执行器注册摘除:执行器销毁时,将会主动上报调度中心并摘除对应的执行器机器信息,提高心跳注册的实时性;
@@ -1479,15 +1449,17 @@
### 6.25 版本 v2.1.0 Release Notes[规划中]
-- 1、[规划中] 移除quartz:精简底层实现,优化已知问题;
+- 1、自研调度组件,移除quartz依赖:一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;
- 触发:单节点周期性触发,运行事件如delayqueue;
- - 调度:集群竞争,负载方式协同处理,竞争-加入时间轮-释放-竞争;
-- 2、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
-- 3、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
-- 4、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
-- 5、调度线程池参数调优;
-- 6、升级xxl-rpc至较新版本,并清理冗余POM;
-- 7、注册表索引优化,缓解锁表问题;
+ - 调度:集群竞争,负载方式协同处理,锁竞争-更新触发信息-推送时间轮-锁释放-锁竞争;
+- 2、底层表结构重构:移除11张quartz相关表,并对现有表结构优化梳理;
+- 3、底层线程模型重构:移除Quartz线程池,降低系统线程与内存开销;
+- 4、用户管理:支持在线管理系统用户,存在管理员、普通用户两种角色;
+- 5、权限管理:执行器维度进行权限控制,管理员拥有全量权限,普通用户需要分配执行器权限后才允许相关操作;
+- 6、调度日志优化:支持设置日志保留天数,过期日志天维度记录报表,并清理;调度报表汇总实时数据和报表;
+- 7、调度线程池参数调优;
+- 8、升级xxl-rpc至较新版本,并清理冗余POM;
+- 9、注册表索引优化,缓解锁表问题;
### TODO LIST
diff --git "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql" "b/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
deleted file mode 100644
index e45e435..0000000
--- "a/doc/db/tables_mysql\050\345\244\207\344\273\275,\350\257\267\345\277\275\347\225\245\051.sql"
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
-#
-# PLEASE consider using mysql with innodb tables to avoid locking issues
-#
-# In your Quartz properties file, you'll need to set
-# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-#
-
-DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
-DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
-DROP TABLE IF EXISTS QRTZ_LOCKS;
-DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_TRIGGERS;
-DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
-DROP TABLE IF EXISTS QRTZ_CALENDARS;
-
-
-CREATE TABLE QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-commit;
diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql
index 40be591..a336377 100644
--- a/doc/db/tables_xxl_job.sql
+++ b/doc/db/tables_xxl_job.sql
@@ -3,153 +3,7 @@
-CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- JOB_CLASS_NAME VARCHAR(250) NOT NULL,
- IS_DURABLE VARCHAR(1) NOT NULL,
- IS_NONCONCURRENT VARCHAR(1) NOT NULL,
- IS_UPDATE_DATA VARCHAR(1) NOT NULL,
- REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- JOB_NAME VARCHAR(200) NOT NULL,
- JOB_GROUP VARCHAR(200) NOT NULL,
- DESCRIPTION VARCHAR(250) NULL,
- NEXT_FIRE_TIME BIGINT(13) NULL,
- PREV_FIRE_TIME BIGINT(13) NULL,
- PRIORITY INTEGER NULL,
- TRIGGER_STATE VARCHAR(16) NOT NULL,
- TRIGGER_TYPE VARCHAR(8) NOT NULL,
- START_TIME BIGINT(13) NOT NULL,
- END_TIME BIGINT(13) NULL,
- CALENDAR_NAME VARCHAR(200) NULL,
- MISFIRE_INSTR SMALLINT(2) NULL,
- JOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
- REFERENCES XXL_JOB_QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPLE_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- REPEAT_COUNT BIGINT(7) NOT NULL,
- REPEAT_INTERVAL BIGINT(12) NOT NULL,
- TIMES_TRIGGERED BIGINT(10) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CRON_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- CRON_EXPRESSION VARCHAR(200) NOT NULL,
- TIME_ZONE_ID VARCHAR(80),
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SIMPROP_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- STR_PROP_1 VARCHAR(512) NULL,
- STR_PROP_2 VARCHAR(512) NULL,
- STR_PROP_3 VARCHAR(512) NULL,
- INT_PROP_1 INT NULL,
- INT_PROP_2 INT NULL,
- LONG_PROP_1 BIGINT NULL,
- LONG_PROP_2 BIGINT NULL,
- DEC_PROP_1 NUMERIC(13,4) NULL,
- DEC_PROP_2 NUMERIC(13,4) NULL,
- BOOL_PROP_1 VARCHAR(1) NULL,
- BOOL_PROP_2 VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_BLOB_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- BLOB_DATA BLOB NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
- FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
- REFERENCES XXL_JOB_QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_CALENDARS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- CALENDAR_NAME VARCHAR(200) NOT NULL,
- CALENDAR BLOB NOT NULL,
- PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_PAUSED_TRIGGER_GRPS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_FIRED_TRIGGERS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- ENTRY_ID VARCHAR(95) NOT NULL,
- TRIGGER_NAME VARCHAR(200) NOT NULL,
- TRIGGER_GROUP VARCHAR(200) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- FIRED_TIME BIGINT(13) NOT NULL,
- SCHED_TIME BIGINT(13) NOT NULL,
- PRIORITY INTEGER NOT NULL,
- STATE VARCHAR(16) NOT NULL,
- JOB_NAME VARCHAR(200) NULL,
- JOB_GROUP VARCHAR(200) NULL,
- IS_NONCONCURRENT VARCHAR(1) NULL,
- REQUESTS_RECOVERY VARCHAR(1) NULL,
- PRIMARY KEY (SCHED_NAME,ENTRY_ID)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_SCHEDULER_STATE
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- INSTANCE_NAME VARCHAR(200) NOT NULL,
- LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
- CHECKIN_INTERVAL BIGINT(13) NOT NULL,
- PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
-);
-
-CREATE TABLE XXL_JOB_QRTZ_LOCKS
- (
- SCHED_NAME VARCHAR(120) NOT NULL,
- LOCK_NAME VARCHAR(40) NOT NULL,
- PRIMARY KEY (SCHED_NAME,LOCK_NAME)
-);
-
-
-
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` (
+CREATE TABLE `XXL_JOB_INFO` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_cron` varchar(128) NOT NULL COMMENT '任务执行CRON',
@@ -169,10 +23,13 @@
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
+ `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
+ `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
+ `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` (
+CREATE TABLE `XXL_JOB_LOG` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
@@ -193,7 +50,7 @@
KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOGGLUE` (
+CREATE TABLE `XXL_JOB_LOGGLUE` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
@@ -204,7 +61,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_REGISTRY` (
+CREATE TABLE `XXL_JOB_REGISTRY` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`registry_group` varchar(255) NOT NULL,
`registry_key` varchar(255) NOT NULL,
@@ -214,7 +71,7 @@
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` (
+CREATE TABLE `XXL_JOB_GROUP` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
@@ -224,7 +81,7 @@
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-CREATE TABLE `XXL_JOB_QRTZ_USER` (
+CREATE TABLE `XXL_JOB_USER` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
@@ -234,10 +91,16 @@
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `XXL_JOB_LOCK` (
+ `lock_name` varchar(50) NOT NULL COMMENT '锁名称',
+ PRIMARY KEY (`lock_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
-INSERT INTO `XXL_JOB_QRTZ_TRIGGER_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
-INSERT INTO `XXL_JOB_QRTZ_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+
+INSERT INTO `XXL_JOB_GROUP`(`id`, `app_name`, `title`, `order`, `address_type`, `address_list`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 1, 0, NULL);
+INSERT INTO `XXL_JOB_INFO`(`id`, `job_group`, `job_cron`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '0 0 0 * * ? *', '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
+INSERT INTO `XXL_JOB_USER`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
+INSERT INTO `XXL_JOB_LOCK` ( `lock_name`) VALUES ( 'schedule_lock');
commit;
diff --git a/pom.xml b/pom.xml
index 28970d8..d07a1aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,7 +37,6 @@
1.32.5.6
- 2.3.13.0.13.1.0
diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml
index 7c201ab..78341a6 100644
--- a/xxl-job-admin/pom.xml
+++ b/xxl-job-admin/pom.xml
@@ -60,13 +60,6 @@
${mysql-connector-java.version}
-
-
- org.quartz-scheduler
- quartz
- ${quartz.version}
-
-
com.xuxueli
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
index ee30181..2a6c738 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobApiController.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.controller;
import com.xxl.job.admin.controller.annotation.PermissionLimit;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.core.biz.AdminBiz;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Controller;
@@ -27,7 +27,7 @@
@RequestMapping(AdminBiz.MAPPING)
@PermissionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- XxlJobDynamicScheduler.invokeAdminService(request, response);
+ XxlJobScheduler.invokeAdminService(request, response);
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
index b6e40b0..97b21dd 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java
@@ -1,10 +1,10 @@
package com.xxl.job.admin.controller;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.exception.XxlJobException;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -136,7 +136,7 @@
@ResponseBody
public ReturnT logDetailCat(String executorAddress, long triggerTime, int logId, int fromLineNum){
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(executorAddress);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
ReturnT logResult = executorBiz.log(triggerTime, logId, fromLineNum);
// is end
@@ -170,7 +170,7 @@
// request of kill
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(log.getExecutorAddress());
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(log.getExecutorAddress());
runResult = executorBiz.kill(jobInfo.getId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
index 8557233..b954771 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobAdminConfig.java
@@ -11,6 +11,7 @@
import org.springframework.mail.javamail.JavaMailSender;
import javax.annotation.Resource;
+import javax.sql.DataSource;
/**
* xxl-job config
@@ -53,6 +54,8 @@
private AdminBiz adminBiz;
@Resource
private JavaMailSender mailSender;
+ @Resource
+ private DataSource dataSource;
public String getI18n() {
@@ -91,4 +94,8 @@
return mailSender;
}
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
deleted file mode 100644
index 0d802bc..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobDynamicSchedulerConfig.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.xxl.job.admin.core.conf;
-
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
-import org.quartz.Scheduler;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
-
-import javax.sql.DataSource;
-
-/**
- * @author xuxueli 2018-10-28 00:18:17
- */
-@Configuration
-public class XxlJobDynamicSchedulerConfig {
-
- @Bean
- public SchedulerFactoryBean getSchedulerFactoryBean(DataSource dataSource){
-
- SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
- schedulerFactory.setDataSource(dataSource);
- schedulerFactory.setAutoStartup(true); // 自动启动
- schedulerFactory.setStartupDelay(20); // 延时启动,应用启动成功后在启动
- schedulerFactory.setOverwriteExistingJobs(true); // 覆盖DB中JOB:true、以数据库中已经存在的为准:false
- schedulerFactory.setApplicationContextSchedulerContextKey("applicationContext");
- schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
-
- return schedulerFactory;
- }
-
- @Bean(initMethod = "start", destroyMethod = "destroy")
- public XxlJobDynamicScheduler getXxlJobDynamicScheduler(SchedulerFactoryBean schedulerFactory){
-
- Scheduler scheduler = schedulerFactory.getScheduler();
-
- XxlJobDynamicScheduler xxlJobDynamicScheduler = new XxlJobDynamicScheduler();
- xxlJobDynamicScheduler.setScheduler(scheduler);
-
- return xxlJobDynamicScheduler;
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
new file mode 100644
index 0000000..e4ef340
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/conf/XxlJobScheduler.java
@@ -0,0 +1,147 @@
+package com.xxl.job.admin.core.conf;
+
+import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+import com.xxl.job.admin.core.thread.JobScheduleHelper;
+import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+import com.xxl.job.admin.core.util.I18nUtil;
+import com.xxl.job.core.biz.AdminBiz;
+import com.xxl.job.core.biz.ExecutorBiz;
+import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+import com.xxl.rpc.remoting.invoker.call.CallType;
+import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+import com.xxl.rpc.remoting.net.NetEnum;
+import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+import com.xxl.rpc.serialize.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author xuxueli 2018-10-28 00:18:17
+ */
+@Configuration
+public class XxlJobScheduler implements InitializingBean, DisposableBean {
+ private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // init i18n
+ initI18n();
+
+ // admin registry monitor run
+ JobRegistryMonitorHelper.getInstance().start();
+
+ // admin monitor run
+ JobFailMonitorHelper.getInstance().start();
+
+ // admin-server
+ initRpcProvider();
+
+ // start-schedule
+ JobScheduleHelper.getInstance().start();
+
+ logger.info(">>>>>>>>> init xxl-job admin success.");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ // stop-schedule
+ JobScheduleHelper.getInstance().toStop();
+
+ // admin trigger pool stop
+ JobTriggerPoolHelper.toStop();
+
+ // admin registry stop
+ JobRegistryMonitorHelper.getInstance().toStop();
+
+ // admin monitor stop
+ JobFailMonitorHelper.getInstance().toStop();
+
+ // admin-server
+ stopRpcProvider();
+ }
+
+ // ---------------------- I18n ----------------------
+
+ private void initI18n(){
+ for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+ item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+ }
+ }
+
+ // ---------------------- admin rpc provider (no server version) ----------------------
+ private static ServletServerHandler servletServerHandler;
+ private void initRpcProvider(){
+ // init
+ XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+ xxlRpcProviderFactory.initConfig(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ null,
+ 0,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null);
+
+ // add services
+ xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+
+ // servlet handler
+ servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+ }
+ private void stopRpcProvider() throws Exception {
+ XxlRpcInvokerFactory.getInstance().stop();
+ }
+ public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ servletServerHandler.handle(null, request, response);
+ }
+
+
+ // ---------------------- executor-client ----------------------
+ private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+ public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+ // valid
+ if (address==null || address.trim().length()==0) {
+ return null;
+ }
+
+ // load-cache
+ address = address.trim();
+ ExecutorBiz executorBiz = executorBizRepository.get(address);
+ if (executorBiz != null) {
+ return executorBiz;
+ }
+
+ // set-cache
+ executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+ NetEnum.NETTY_HTTP,
+ Serializer.SerializeEnum.HESSIAN.getSerializer(),
+ CallType.SYNC,
+ LoadBalance.ROUND,
+ ExecutorBiz.class,
+ null,
+ 5000,
+ address,
+ XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+ null,
+ null).getObject();
+
+ executorBizRepository.put(address, executorBiz);
+ return executorBiz;
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
new file mode 100644
index 0000000..d0f7f38
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/cron/CronExpression.java
@@ -0,0 +1,1668 @@
+/*
+ * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package com.xxl.job.admin.core.cron;
+
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+/**
+ * Provides a parser and evaluator for unix-like cron expressions. Cron
+ * expressions provide the ability to specify complex time combinations such as
+ * "At 8:00am every Monday through Friday" or "At 1:30am every
+ * last Friday of the month".
+ *
+ * Cron expressions are comprised of 6 required fields and one optional field
+ * separated by white space. The fields respectively are described as follows:
+ *
+ *
+ *
+ *
Field Name
+ *
+ *
Allowed Values
+ *
+ *
Allowed Special Characters
+ *
+ *
+ *
Seconds
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Minutes
+ *
+ *
0-59
+ *
+ *
, - * /
+ *
+ *
+ *
Hours
+ *
+ *
0-23
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-month
+ *
+ *
1-31
+ *
+ *
, - * ? / L W
+ *
+ *
+ *
Month
+ *
+ *
0-11 or JAN-DEC
+ *
+ *
, - * /
+ *
+ *
+ *
Day-of-Week
+ *
+ *
1-7 or SUN-SAT
+ *
+ *
, - * ? / L #
+ *
+ *
+ *
Year (Optional)
+ *
+ *
empty, 1970-2199
+ *
+ *
, - * /
+ *
+ *
+ *
+ * The '*' character is used to specify all values. For example, "*"
+ * in the minute field means "every minute".
+ *
+ * The '?' character is allowed for the day-of-month and day-of-week fields. It
+ * is used to specify 'no specific value'. This is useful when you need to
+ * specify something in one of the two fields, but not the other.
+ *
+ * The '-' character is used to specify ranges For example "10-12" in
+ * the hour field means "the hours 10, 11 and 12".
+ *
+ * The ',' character is used to specify additional values. For example
+ * "MON,WED,FRI" in the day-of-week field means "the days Monday,
+ * Wednesday, and Friday".
+ *
+ * The '/' character is used to specify increments. For example "0/15"
+ * in the seconds field means "the seconds 0, 15, 30, and 45". And
+ * "5/15" in the seconds field means "the seconds 5, 20, 35, and
+ * 50". Specifying '*' before the '/' is equivalent to specifying 0 is
+ * the value to start with. Essentially, for each field in the expression, there
+ * is a set of numbers that can be turned on or off. For seconds and minutes,
+ * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
+ * 31, and for months 0 to 11 (JAN to DEC). The "/" character simply helps you turn
+ * on every "nth" value in the given set. Thus "7/6" in the
+ * month field only turns on month "7", it does NOT mean every 6th
+ * month, please note that subtlety.
+ *
+ * The 'L' character is allowed for the day-of-month and day-of-week fields.
+ * This character is short-hand for "last", but it has different
+ * meaning in each of the two fields. For example, the value "L" in
+ * the day-of-month field means "the last day of the month" - day 31
+ * for January, day 28 for February on non-leap years. If used in the
+ * day-of-week field by itself, it simply means "7" or
+ * "SAT". But if used in the day-of-week field after another value, it
+ * means "the last xxx day of the month" - for example "6L"
+ * means "the last friday of the month". You can also specify an offset
+ * from the last day of the month, such as "L-3" which would mean the third-to-last
+ * day of the calendar month. When using the 'L' option, it is important not to
+ * specify lists, or ranges of values, as you'll get confusing/unexpected results.
+ *
+ * The 'W' character is allowed for the day-of-month field. This character
+ * is used to specify the weekday (Monday-Friday) nearest the given day. As an
+ * example, if you were to specify "15W" as the value for the
+ * day-of-month field, the meaning is: "the nearest weekday to the 15th of
+ * the month". So if the 15th is a Saturday, the trigger will fire on
+ * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
+ * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th.
+ * However if you specify "1W" as the value for day-of-month, and the
+ * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not
+ * 'jump' over the boundary of a month's days. The 'W' character can only be
+ * specified when the day-of-month is a single day, not a range or list of days.
+ *
+ * The 'L' and 'W' characters can also be combined for the day-of-month
+ * expression to yield 'LW', which translates to "last weekday of the
+ * month".
+ *
+ * The '#' character is allowed for the day-of-week field. This character is
+ * used to specify "the nth" XXX day of the month. For example, the
+ * value of "6#3" in the day-of-week field means the third Friday of
+ * the month (day 6 = Friday and "#3" = the 3rd one in the month).
+ * Other examples: "2#1" = the first Monday of the month and
+ * "4#5" = the fifth Wednesday of the month. Note that if you specify
+ * "#5" and there is not 5 of the given day-of-week in the month, then
+ * no firing will occur that month. If the '#' character is used, there can
+ * only be one expression in the day-of-week field ("3#1,6#3" is
+ * not valid, since there are two expressions).
+ *
+ *
+ *
+ * The legal characters and the names of months and days of the week are not
+ * case sensitive.
+ *
+ *
+ * NOTES:
+ *
+ *
Support for specifying both a day-of-week and a day-of-month value is
+ * not complete (you'll need to use the '?' character in one of these fields).
+ *
+ *
Overflowing ranges is supported - that is, having a larger number on
+ * the left hand side than the right. You might do 22-2 to catch 10 o'clock
+ * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
+ * very important to note that overuse of overflowing ranges creates ranges
+ * that don't make sense and no effort has been made to determine which
+ * interpretation CronExpression chooses. An example would be
+ * "0 0 14-6 ? * FRI-MON".
+ *
+ *
+ *
+ *
+ * @author Sharada Jambula, James House
+ * @author Contributions from Mads Henderson
+ * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
+ *
+ * Borrowed from quartz v2.3.1
+ *
+ */
+public final class CronExpression implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 12423409423L;
+
+ protected static final int SECOND = 0;
+ protected static final int MINUTE = 1;
+ protected static final int HOUR = 2;
+ protected static final int DAY_OF_MONTH = 3;
+ protected static final int MONTH = 4;
+ protected static final int DAY_OF_WEEK = 5;
+ protected static final int YEAR = 6;
+ protected static final int ALL_SPEC_INT = 99; // '*'
+ protected static final int NO_SPEC_INT = 98; // '?'
+ protected static final Integer ALL_SPEC = ALL_SPEC_INT;
+ protected static final Integer NO_SPEC = NO_SPEC_INT;
+
+ protected static final Map monthMap = new HashMap(20);
+ protected static final Map dayMap = new HashMap(60);
+ static {
+ monthMap.put("JAN", 0);
+ monthMap.put("FEB", 1);
+ monthMap.put("MAR", 2);
+ monthMap.put("APR", 3);
+ monthMap.put("MAY", 4);
+ monthMap.put("JUN", 5);
+ monthMap.put("JUL", 6);
+ monthMap.put("AUG", 7);
+ monthMap.put("SEP", 8);
+ monthMap.put("OCT", 9);
+ monthMap.put("NOV", 10);
+ monthMap.put("DEC", 11);
+
+ dayMap.put("SUN", 1);
+ dayMap.put("MON", 2);
+ dayMap.put("TUE", 3);
+ dayMap.put("WED", 4);
+ dayMap.put("THU", 5);
+ dayMap.put("FRI", 6);
+ dayMap.put("SAT", 7);
+ }
+
+ private final String cronExpression;
+ private TimeZone timeZone = null;
+ protected transient TreeSet seconds;
+ protected transient TreeSet minutes;
+ protected transient TreeSet hours;
+ protected transient TreeSet daysOfMonth;
+ protected transient TreeSet months;
+ protected transient TreeSet daysOfWeek;
+ protected transient TreeSet years;
+
+ protected transient boolean lastdayOfWeek = false;
+ protected transient int nthdayOfWeek = 0;
+ protected transient boolean lastdayOfMonth = false;
+ protected transient boolean nearestWeekday = false;
+ protected transient int lastdayOffset = 0;
+ protected transient boolean expressionParsed = false;
+
+ public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
+
+ /**
+ * Constructs a new CronExpression based on the specified
+ * parameter.
+ *
+ * @param cronExpression String representation of the cron expression the
+ * new object should represent
+ * @throws java.text.ParseException
+ * if the string expression cannot be parsed into a valid
+ * CronExpression
+ */
+ public CronExpression(String cronExpression) throws ParseException {
+ if (cronExpression == null) {
+ throw new IllegalArgumentException("cronExpression cannot be null");
+ }
+
+ this.cronExpression = cronExpression.toUpperCase(Locale.US);
+
+ buildExpression(this.cronExpression);
+ }
+
+ /**
+ * Constructs a new {@code CronExpression} as a copy of an existing
+ * instance.
+ *
+ * @param expression
+ * The existing cron expression to be copied
+ */
+ public CronExpression(CronExpression expression) {
+ /*
+ * We don't call the other constructor here since we need to swallow the
+ * ParseException. We also elide some of the sanity checking as it is
+ * not logically trippable.
+ */
+ this.cronExpression = expression.getCronExpression();
+ try {
+ buildExpression(cronExpression);
+ } catch (ParseException ex) {
+ throw new AssertionError();
+ }
+ if (expression.getTimeZone() != null) {
+ setTimeZone((TimeZone) expression.getTimeZone().clone());
+ }
+ }
+
+ /**
+ * Indicates whether the given date satisfies the cron expression. Note that
+ * milliseconds are ignored, so two Dates falling on different milliseconds
+ * of the same second will always have the same result here.
+ *
+ * @param date the date to evaluate
+ * @return a boolean indicating whether the given date satisfies the cron
+ * expression
+ */
+ public boolean isSatisfiedBy(Date date) {
+ Calendar testDateCal = Calendar.getInstance(getTimeZone());
+ testDateCal.setTime(date);
+ testDateCal.set(Calendar.MILLISECOND, 0);
+ Date originalDate = testDateCal.getTime();
+
+ testDateCal.add(Calendar.SECOND, -1);
+
+ Date timeAfter = getTimeAfter(testDateCal.getTime());
+
+ return ((timeAfter != null) && (timeAfter.equals(originalDate)));
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which
+ * satisfies the cron expression.
+ *
+ * @param date the date/time at which to begin the search for the next valid
+ * date/time
+ * @return the next valid date/time
+ */
+ public Date getNextValidTimeAfter(Date date) {
+ return getTimeAfter(date);
+ }
+
+ /**
+ * Returns the next date/time after the given date/time which does
+ * not satisfy the expression
+ *
+ * @param date the date/time at which to begin the search for the next
+ * invalid date/time
+ * @return the next valid date/time
+ */
+ public Date getNextInvalidTimeAfter(Date date) {
+ long difference = 1000;
+
+ //move back to the nearest second so differences will be accurate
+ Calendar adjustCal = Calendar.getInstance(getTimeZone());
+ adjustCal.setTime(date);
+ adjustCal.set(Calendar.MILLISECOND, 0);
+ Date lastDate = adjustCal.getTime();
+
+ Date newDate;
+
+ //FUTURE_TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
+
+ //keep getting the next included time until it's farther than one second
+ // apart. At that point, lastDate is the last valid fire time. We return
+ // the second immediately following it.
+ while (difference == 1000) {
+ newDate = getTimeAfter(lastDate);
+ if(newDate == null)
+ break;
+
+ difference = newDate.getTime() - lastDate.getTime();
+
+ if (difference == 1000) {
+ lastDate = newDate;
+ }
+ }
+
+ return new Date(lastDate.getTime() + 1000);
+ }
+
+ /**
+ * Returns the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ return timeZone;
+ }
+
+ /**
+ * Sets the time zone for which this CronExpression
+ * will be resolved.
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * Returns the string representation of the CronExpression
+ *
+ * @return a string representation of the CronExpression
+ */
+ @Override
+ public String toString() {
+ return cronExpression;
+ }
+
+ /**
+ * Indicates whether the specified cron expression can be parsed into a
+ * valid cron expression
+ *
+ * @param cronExpression the expression to evaluate
+ * @return a boolean indicating whether the given expression is a valid cron
+ * expression
+ */
+ public static boolean isValidExpression(String cronExpression) {
+
+ try {
+ new CronExpression(cronExpression);
+ } catch (ParseException pe) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void validateExpression(String cronExpression) throws ParseException {
+
+ new CronExpression(cronExpression);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Expression Parsing Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ protected void buildExpression(String expression) throws ParseException {
+ expressionParsed = true;
+
+ try {
+
+ if (seconds == null) {
+ seconds = new TreeSet();
+ }
+ if (minutes == null) {
+ minutes = new TreeSet();
+ }
+ if (hours == null) {
+ hours = new TreeSet();
+ }
+ if (daysOfMonth == null) {
+ daysOfMonth = new TreeSet();
+ }
+ if (months == null) {
+ months = new TreeSet();
+ }
+ if (daysOfWeek == null) {
+ daysOfWeek = new TreeSet();
+ }
+ if (years == null) {
+ years = new TreeSet();
+ }
+
+ int exprOn = SECOND;
+
+ StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
+ false);
+
+ while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
+ String expr = exprsTok.nextToken().trim();
+
+ // throw an exception if L is used with other days of the month
+ if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
+ }
+ // throw an exception if L is used with other days of the week
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
+ throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
+ }
+ if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
+ throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
+ }
+
+ StringTokenizer vTok = new StringTokenizer(expr, ",");
+ while (vTok.hasMoreTokens()) {
+ String v = vTok.nextToken();
+ storeExpressionVals(0, v, exprOn);
+ }
+
+ exprOn++;
+ }
+
+ if (exprOn <= DAY_OF_WEEK) {
+ throw new ParseException("Unexpected end of expression.",
+ expression.length());
+ }
+
+ if (exprOn <= YEAR) {
+ storeExpressionVals(0, "*", YEAR);
+ }
+
+ TreeSet dow = getSet(DAY_OF_WEEK);
+ TreeSet dom = getSet(DAY_OF_MONTH);
+
+ // Copying the logic from the UnsupportedOperationException below
+ boolean dayOfMSpec = !dom.contains(NO_SPEC);
+ boolean dayOfWSpec = !dow.contains(NO_SPEC);
+
+ if (!dayOfMSpec || dayOfWSpec) {
+ if (!dayOfWSpec || dayOfMSpec) {
+ throw new ParseException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
+ }
+ }
+ } catch (ParseException pe) {
+ throw pe;
+ } catch (Exception e) {
+ throw new ParseException("Illegal cron expression format ("
+ + e.toString() + ")", 0);
+ }
+ }
+
+ protected int storeExpressionVals(int pos, String s, int type)
+ throws ParseException {
+
+ int incr = 0;
+ int i = skipWhiteSpace(pos, s);
+ if (i >= s.length()) {
+ return i;
+ }
+ char c = s.charAt(i);
+ if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW")) && (!s.matches("^L-[0-9]*[W]?"))) {
+ String sub = s.substring(i, i + 3);
+ int sval = -1;
+ int eval = -1;
+ if (type == MONTH) {
+ sval = getMonthNumber(sub) + 1;
+ if (sval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getMonthNumber(sub) + 1;
+ if (eval <= 0) {
+ throw new ParseException("Invalid Month value: '" + sub + "'", i);
+ }
+ }
+ }
+ } else if (type == DAY_OF_WEEK) {
+ sval = getDayOfWeekNumber(sub);
+ if (sval < 0) {
+ throw new ParseException("Invalid Day-of-Week value: '"
+ + sub + "'", i);
+ }
+ if (s.length() > i + 3) {
+ c = s.charAt(i + 3);
+ if (c == '-') {
+ i += 4;
+ sub = s.substring(i, i + 3);
+ eval = getDayOfWeekNumber(sub);
+ if (eval < 0) {
+ throw new ParseException(
+ "Invalid Day-of-Week value: '" + sub
+ + "'", i);
+ }
+ } else if (c == '#') {
+ try {
+ i += 4;
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+ } else if (c == 'L') {
+ lastdayOfWeek = true;
+ i++;
+ }
+ }
+
+ } else {
+ throw new ParseException(
+ "Illegal characters for this position: '" + sub + "'",
+ i);
+ }
+ if (eval != -1) {
+ incr = 1;
+ }
+ addToSet(sval, eval, incr, type);
+ return (i + 3);
+ }
+
+ if (c == '?') {
+ i++;
+ if ((i + 1) < s.length()
+ && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
+ throw new ParseException("Illegal character after '?': "
+ + s.charAt(i), i);
+ }
+ if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month or Day-of-Week.",
+ i);
+ }
+ if (type == DAY_OF_WEEK && !lastdayOfMonth) {
+ int val = daysOfMonth.last();
+ if (val == NO_SPEC_INT) {
+ throw new ParseException(
+ "'?' can only be specified for Day-of-Month -OR- Day-of-Week.",
+ i);
+ }
+ }
+
+ addToSet(NO_SPEC_INT, -1, 0, type);
+ return i;
+ }
+
+ if (c == '*' || c == '/') {
+ if (c == '*' && (i + 1) >= s.length()) {
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i + 1;
+ } else if (c == '/'
+ && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
+ .charAt(i + 1) == '\t')) {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ } else if (c == '*') {
+ i++;
+ }
+ c = s.charAt(i);
+ if (c == '/') { // is an increment specified?
+ i++;
+ if (i >= s.length()) {
+ throw new ParseException("Unexpected end of string.", i);
+ }
+
+ incr = getNumericValue(s, i);
+
+ i++;
+ if (incr > 10) {
+ i++;
+ }
+ checkIncrementRange(incr, type, i);
+ } else {
+ incr = 1;
+ }
+
+ addToSet(ALL_SPEC_INT, -1, incr, type);
+ return i;
+ } else if (c == 'L') {
+ i++;
+ if (type == DAY_OF_MONTH) {
+ lastdayOfMonth = true;
+ }
+ if (type == DAY_OF_WEEK) {
+ addToSet(7, 7, 0, type);
+ }
+ if(type == DAY_OF_MONTH && s.length() > i) {
+ c = s.charAt(i);
+ if(c == '-') {
+ ValueSet vs = getValue(0, s, i+1);
+ lastdayOffset = vs.value;
+ if(lastdayOffset > 30)
+ throw new ParseException("Offset from last day must be <= 30", i+1);
+ i = vs.pos;
+ }
+ if(s.length() > i) {
+ c = s.charAt(i);
+ if(c == 'W') {
+ nearestWeekday = true;
+ i++;
+ }
+ }
+ }
+ return i;
+ } else if (c >= '0' && c <= '9') {
+ int val = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, -1, -1, type);
+ } else {
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(val, s, i);
+ val = vs.value;
+ i = vs.pos;
+ }
+ i = checkNext(i, s, val, type);
+ return i;
+ }
+ } else {
+ throw new ParseException("Unexpected character: " + c, i);
+ }
+
+ return i;
+ }
+
+ private void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {
+ if (incr > 59 && (type == SECOND || type == MINUTE)) {
+ throw new ParseException("Increment > 60 : " + incr, idxPos);
+ } else if (incr > 23 && (type == HOUR)) {
+ throw new ParseException("Increment > 24 : " + incr, idxPos);
+ } else if (incr > 31 && (type == DAY_OF_MONTH)) {
+ throw new ParseException("Increment > 31 : " + incr, idxPos);
+ } else if (incr > 7 && (type == DAY_OF_WEEK)) {
+ throw new ParseException("Increment > 7 : " + incr, idxPos);
+ } else if (incr > 12 && (type == MONTH)) {
+ throw new ParseException("Increment > 12 : " + incr, idxPos);
+ }
+ }
+
+ protected int checkNext(int pos, String s, int val, int type)
+ throws ParseException {
+
+ int end = -1;
+ int i = pos;
+
+ if (i >= s.length()) {
+ addToSet(val, end, -1, type);
+ return i;
+ }
+
+ char c = s.charAt(pos);
+
+ if (c == 'L') {
+ if (type == DAY_OF_WEEK) {
+ if(val < 1 || val > 7)
+ throw new ParseException("Day-of-Week values must be between 1 and 7", -1);
+ lastdayOfWeek = true;
+ } else {
+ throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
+ }
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == 'W') {
+ if (type == DAY_OF_MONTH) {
+ nearestWeekday = true;
+ } else {
+ throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
+ }
+ if(val > 31)
+ throw new ParseException("The 'W' option does not make sense with values larger than 31 (max number of days in a month)", i);
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '#') {
+ if (type != DAY_OF_WEEK) {
+ throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
+ }
+ i++;
+ try {
+ nthdayOfWeek = Integer.parseInt(s.substring(i));
+ if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ throw new ParseException(
+ "A numeric value between 1 and 5 must follow the '#' option",
+ i);
+ }
+
+ TreeSet set = getSet(type);
+ set.add(val);
+ i++;
+ return i;
+ }
+
+ if (c == '-') {
+ i++;
+ c = s.charAt(i);
+ int v = Integer.parseInt(String.valueOf(c));
+ end = v;
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v, s, i);
+ end = vs.value;
+ i = vs.pos;
+ }
+ if (i < s.length() && ((c = s.charAt(i)) == '/')) {
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ } else {
+ addToSet(val, end, 1, type);
+ return i;
+ }
+ }
+
+ if (c == '/') {
+ if ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t') {
+ throw new ParseException("'/' must be followed by an integer.", i);
+ }
+
+ i++;
+ c = s.charAt(i);
+ int v2 = Integer.parseInt(String.valueOf(c));
+ i++;
+ if (i >= s.length()) {
+ checkIncrementRange(v2, type, i);
+ addToSet(val, end, v2, type);
+ return i;
+ }
+ c = s.charAt(i);
+ if (c >= '0' && c <= '9') {
+ ValueSet vs = getValue(v2, s, i);
+ int v3 = vs.value;
+ checkIncrementRange(v3, type, i);
+ addToSet(val, end, v3, type);
+ i = vs.pos;
+ return i;
+ } else {
+ throw new ParseException("Unexpected character '" + c + "' after '/'", i);
+ }
+ }
+
+ addToSet(val, end, 0, type);
+ i++;
+ return i;
+ }
+
+ public String getCronExpression() {
+ return cronExpression;
+ }
+
+ public String getExpressionSummary() {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append("seconds: ");
+ buf.append(getExpressionSetSummary(seconds));
+ buf.append("\n");
+ buf.append("minutes: ");
+ buf.append(getExpressionSetSummary(minutes));
+ buf.append("\n");
+ buf.append("hours: ");
+ buf.append(getExpressionSetSummary(hours));
+ buf.append("\n");
+ buf.append("daysOfMonth: ");
+ buf.append(getExpressionSetSummary(daysOfMonth));
+ buf.append("\n");
+ buf.append("months: ");
+ buf.append(getExpressionSetSummary(months));
+ buf.append("\n");
+ buf.append("daysOfWeek: ");
+ buf.append(getExpressionSetSummary(daysOfWeek));
+ buf.append("\n");
+ buf.append("lastdayOfWeek: ");
+ buf.append(lastdayOfWeek);
+ buf.append("\n");
+ buf.append("nearestWeekday: ");
+ buf.append(nearestWeekday);
+ buf.append("\n");
+ buf.append("NthDayOfWeek: ");
+ buf.append(nthdayOfWeek);
+ buf.append("\n");
+ buf.append("lastdayOfMonth: ");
+ buf.append(lastdayOfMonth);
+ buf.append("\n");
+ buf.append("years: ");
+ buf.append(getExpressionSetSummary(years));
+ buf.append("\n");
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.Set set) {
+
+ if (set.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (set.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = set.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected String getExpressionSetSummary(java.util.ArrayList list) {
+
+ if (list.contains(NO_SPEC)) {
+ return "?";
+ }
+ if (list.contains(ALL_SPEC)) {
+ return "*";
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ Iterator itr = list.iterator();
+ boolean first = true;
+ while (itr.hasNext()) {
+ Integer iVal = itr.next();
+ String val = iVal.toString();
+ if (!first) {
+ buf.append(",");
+ }
+ buf.append(val);
+ first = false;
+ }
+
+ return buf.toString();
+ }
+
+ protected int skipWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected int findNextWhiteSpace(int i, String s) {
+ for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
+ ;
+ }
+
+ return i;
+ }
+
+ protected void addToSet(int val, int end, int incr, int type)
+ throws ParseException {
+
+ TreeSet set = getSet(type);
+
+ if (type == SECOND || type == MINUTE) {
+ if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Minute and Second values must be between 0 and 59",
+ -1);
+ }
+ } else if (type == HOUR) {
+ if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Hour values must be between 0 and 23", -1);
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day of month values must be between 1 and 31", -1);
+ }
+ } else if (type == MONTH) {
+ if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
+ throw new ParseException(
+ "Month values must be between 1 and 12", -1);
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
+ && (val != NO_SPEC_INT)) {
+ throw new ParseException(
+ "Day-of-Week values must be between 1 and 7", -1);
+ }
+ }
+
+ if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
+ if (val != -1) {
+ set.add(val);
+ } else {
+ set.add(NO_SPEC);
+ }
+
+ return;
+ }
+
+ int startAt = val;
+ int stopAt = end;
+
+ if (val == ALL_SPEC_INT && incr <= 0) {
+ incr = 1;
+ set.add(ALL_SPEC); // put in a marker, but also fill values
+ }
+
+ if (type == SECOND || type == MINUTE) {
+ if (stopAt == -1) {
+ stopAt = 59;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == HOUR) {
+ if (stopAt == -1) {
+ stopAt = 23;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 0;
+ }
+ } else if (type == DAY_OF_MONTH) {
+ if (stopAt == -1) {
+ stopAt = 31;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == MONTH) {
+ if (stopAt == -1) {
+ stopAt = 12;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == DAY_OF_WEEK) {
+ if (stopAt == -1) {
+ stopAt = 7;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1;
+ }
+ } else if (type == YEAR) {
+ if (stopAt == -1) {
+ stopAt = MAX_YEAR;
+ }
+ if (startAt == -1 || startAt == ALL_SPEC_INT) {
+ startAt = 1970;
+ }
+ }
+
+ // if the end of the range is before the start, then we need to overflow into
+ // the next day, month etc. This is done by adding the maximum amount for that
+ // type, and using modulus max to determine the value being added.
+ int max = -1;
+ if (stopAt < startAt) {
+ switch (type) {
+ case SECOND : max = 60; break;
+ case MINUTE : max = 60; break;
+ case HOUR : max = 24; break;
+ case MONTH : max = 12; break;
+ case DAY_OF_WEEK : max = 7; break;
+ case DAY_OF_MONTH : max = 31; break;
+ case YEAR : throw new IllegalArgumentException("Start year must be less than stop year");
+ default : throw new IllegalArgumentException("Unexpected type encountered");
+ }
+ stopAt += max;
+ }
+
+ for (int i = startAt; i <= stopAt; i += incr) {
+ if (max == -1) {
+ // ie: there's no max to overflow over
+ set.add(i);
+ } else {
+ // take the modulus to get the real value
+ int i2 = i % max;
+
+ // 1-indexed ranges should not include 0, and should include their max
+ if (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {
+ i2 = max;
+ }
+
+ set.add(i2);
+ }
+ }
+ }
+
+ TreeSet getSet(int type) {
+ switch (type) {
+ case SECOND:
+ return seconds;
+ case MINUTE:
+ return minutes;
+ case HOUR:
+ return hours;
+ case DAY_OF_MONTH:
+ return daysOfMonth;
+ case MONTH:
+ return months;
+ case DAY_OF_WEEK:
+ return daysOfWeek;
+ case YEAR:
+ return years;
+ default:
+ return null;
+ }
+ }
+
+ protected ValueSet getValue(int v, String s, int i) {
+ char c = s.charAt(i);
+ StringBuilder s1 = new StringBuilder(String.valueOf(v));
+ while (c >= '0' && c <= '9') {
+ s1.append(c);
+ i++;
+ if (i >= s.length()) {
+ break;
+ }
+ c = s.charAt(i);
+ }
+ ValueSet val = new ValueSet();
+
+ val.pos = (i < s.length()) ? i : i + 1;
+ val.value = Integer.parseInt(s1.toString());
+ return val;
+ }
+
+ protected int getNumericValue(String s, int i) {
+ int endOfVal = findNextWhiteSpace(i, s);
+ String val = s.substring(i, endOfVal);
+ return Integer.parseInt(val);
+ }
+
+ protected int getMonthNumber(String s) {
+ Integer integer = monthMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ protected int getDayOfWeekNumber(String s) {
+ Integer integer = dayMap.get(s);
+
+ if (integer == null) {
+ return -1;
+ }
+
+ return integer;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Computation Functions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+
+ public Date getTimeAfter(Date afterTime) {
+
+ // Computation is based on Gregorian year only.
+ Calendar cl = new java.util.GregorianCalendar(getTimeZone());
+
+ // move ahead one second, since we're computing the time *after* the
+ // given time
+ afterTime = new Date(afterTime.getTime() + 1000);
+ // CronTrigger does not deal with milliseconds
+ cl.setTime(afterTime);
+ cl.set(Calendar.MILLISECOND, 0);
+
+ boolean gotOne = false;
+ // loop until we've computed the next time, or we've past the endTime
+ while (!gotOne) {
+
+ //if (endTime != null && cl.getTime().after(endTime)) return null;
+ if(cl.get(Calendar.YEAR) > 2999) { // prevent endless loop...
+ return null;
+ }
+
+ SortedSet st = null;
+ int t = 0;
+
+ int sec = cl.get(Calendar.SECOND);
+ int min = cl.get(Calendar.MINUTE);
+
+ // get second.................................................
+ st = seconds.tailSet(sec);
+ if (st != null && st.size() != 0) {
+ sec = st.first();
+ } else {
+ sec = seconds.first();
+ min++;
+ cl.set(Calendar.MINUTE, min);
+ }
+ cl.set(Calendar.SECOND, sec);
+
+ min = cl.get(Calendar.MINUTE);
+ int hr = cl.get(Calendar.HOUR_OF_DAY);
+ t = -1;
+
+ // get minute.................................................
+ st = minutes.tailSet(min);
+ if (st != null && st.size() != 0) {
+ t = min;
+ min = st.first();
+ } else {
+ min = minutes.first();
+ hr++;
+ }
+ if (min != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, min);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.MINUTE, min);
+
+ hr = cl.get(Calendar.HOUR_OF_DAY);
+ int day = cl.get(Calendar.DAY_OF_MONTH);
+ t = -1;
+
+ // get hour...................................................
+ st = hours.tailSet(hr);
+ if (st != null && st.size() != 0) {
+ t = hr;
+ hr = st.first();
+ } else {
+ hr = hours.first();
+ day++;
+ }
+ if (hr != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ setCalendarHour(cl, hr);
+ continue;
+ }
+ cl.set(Calendar.HOUR_OF_DAY, hr);
+
+ day = cl.get(Calendar.DAY_OF_MONTH);
+ int mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ t = -1;
+ int tmon = mon;
+
+ // get day...................................................
+ boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
+ boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
+ if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
+ st = daysOfMonth.tailSet(day);
+ if (lastdayOfMonth) {
+ if(!nearestWeekday) {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+ if(t > day) {
+ mon++;
+ if(mon > 12) {
+ mon = 1;
+ tmon = 3333; // ensure test of mon != tmon further below fails
+ cl.add(Calendar.YEAR, 1);
+ }
+ day = 1;
+ }
+ } else {
+ t = day;
+ day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ day -= lastdayOffset;
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = 1;
+ mon++;
+ }
+ }
+ } else if(nearestWeekday) {
+ t = day;
+ day = daysOfMonth.first();
+
+ java.util.Calendar tcal = java.util.Calendar.getInstance(getTimeZone());
+ tcal.set(Calendar.SECOND, 0);
+ tcal.set(Calendar.MINUTE, 0);
+ tcal.set(Calendar.HOUR_OF_DAY, 0);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
+
+ int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ int dow = tcal.get(Calendar.DAY_OF_WEEK);
+
+ if(dow == Calendar.SATURDAY && day == 1) {
+ day += 2;
+ } else if(dow == Calendar.SATURDAY) {
+ day -= 1;
+ } else if(dow == Calendar.SUNDAY && day == ldom) {
+ day -= 2;
+ } else if(dow == Calendar.SUNDAY) {
+ day += 1;
+ }
+
+
+ tcal.set(Calendar.SECOND, sec);
+ tcal.set(Calendar.MINUTE, min);
+ tcal.set(Calendar.HOUR_OF_DAY, hr);
+ tcal.set(Calendar.DAY_OF_MONTH, day);
+ tcal.set(Calendar.MONTH, mon - 1);
+ Date nTime = tcal.getTime();
+ if(nTime.before(afterTime)) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else if (st != null && st.size() != 0) {
+ t = day;
+ day = st.first();
+ // make sure we don't over-run a short month, such as february
+ int lastDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+ if (day > lastDay) {
+ day = daysOfMonth.first();
+ mon++;
+ }
+ } else {
+ day = daysOfMonth.first();
+ mon++;
+ }
+
+ if (day != t || mon != tmon) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we
+ // are 1-based
+ continue;
+ }
+ } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
+ if (lastdayOfWeek) { // are we looking for the last XXX day of
+ // the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // did we already miss the
+ // last one?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ }
+
+ // find date of last occurrence of this day in this month...
+ while ((day + daysToAdd + 7) <= lDay) {
+ daysToAdd += 7;
+ }
+
+ day += daysToAdd;
+
+ if (daysToAdd > 0) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are not promoting the month
+ continue;
+ }
+
+ } else if (nthdayOfWeek != 0) {
+ // are we looking for the Nth XXX day in the month?
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ } else if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ boolean dayShifted = false;
+ if (daysToAdd > 0) {
+ dayShifted = true;
+ }
+
+ day += daysToAdd;
+ int weekOfMonth = day / 7;
+ if (day % 7 > 0) {
+ weekOfMonth++;
+ }
+
+ daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
+ day += daysToAdd;
+ if (daysToAdd < 0
+ || day > getLastDayOfMonth(mon, cl
+ .get(Calendar.YEAR))) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0 || dayShifted) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' here because we are NOT promoting the month
+ continue;
+ }
+ } else {
+ int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
+ int dow = daysOfWeek.first(); // desired
+ // d-o-w
+ st = daysOfWeek.tailSet(cDow);
+ if (st != null && st.size() > 0) {
+ dow = st.first();
+ }
+
+ int daysToAdd = 0;
+ if (cDow < dow) {
+ daysToAdd = dow - cDow;
+ }
+ if (cDow > dow) {
+ daysToAdd = dow + (7 - cDow);
+ }
+
+ int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
+
+ if (day + daysToAdd > lDay) { // will we pass the end of
+ // the month?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon);
+ // no '- 1' here because we are promoting the month
+ continue;
+ } else if (daysToAdd > 0) { // are we swithing days?
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field,
+ // and we are 1-based
+ continue;
+ }
+ }
+ } else { // dayOfWSpec && !dayOfMSpec
+ throw new UnsupportedOperationException(
+ "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
+ }
+ cl.set(Calendar.DAY_OF_MONTH, day);
+
+ mon = cl.get(Calendar.MONTH) + 1;
+ // '+ 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ int year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // test for expressions that never generate a valid fire date,
+ // but keep looping...
+ if (year > MAX_YEAR) {
+ return null;
+ }
+
+ // get month...................................................
+ st = months.tailSet(mon);
+ if (st != null && st.size() != 0) {
+ t = mon;
+ mon = st.first();
+ } else {
+ mon = months.first();
+ year++;
+ }
+ if (mon != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.MONTH, mon - 1);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+
+ year = cl.get(Calendar.YEAR);
+ t = -1;
+
+ // get year...................................................
+ st = years.tailSet(year);
+ if (st != null && st.size() != 0) {
+ t = year;
+ year = st.first();
+ } else {
+ return null; // ran out of years...
+ }
+
+ if (year != t) {
+ cl.set(Calendar.SECOND, 0);
+ cl.set(Calendar.MINUTE, 0);
+ cl.set(Calendar.HOUR_OF_DAY, 0);
+ cl.set(Calendar.DAY_OF_MONTH, 1);
+ cl.set(Calendar.MONTH, 0);
+ // '- 1' because calendar is 0-based for this field, and we are
+ // 1-based
+ cl.set(Calendar.YEAR, year);
+ continue;
+ }
+ cl.set(Calendar.YEAR, year);
+
+ gotOne = true;
+ } // while( !done )
+
+ return cl.getTime();
+ }
+
+ /**
+ * Advance the calendar to the particular hour paying particular attention
+ * to daylight saving problems.
+ *
+ * @param cal the calendar to operate on
+ * @param hour the hour to set
+ */
+ protected void setCalendarHour(Calendar cal, int hour) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
+ cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
+ }
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the time before the given time
+ * that the CronExpression matches.
+ */
+ public Date getTimeBefore(Date endTime) {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ /**
+ * NOT YET IMPLEMENTED: Returns the final time that the
+ * CronExpression will match.
+ */
+ public Date getFinalFireTime() {
+ // FUTURE_TODO: implement QUARTZ-423
+ return null;
+ }
+
+ protected boolean isLeapYear(int year) {
+ return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
+ }
+
+ protected int getLastDayOfMonth(int monthNum, int year) {
+
+ switch (monthNum) {
+ case 1:
+ return 31;
+ case 2:
+ return (isLeapYear(year)) ? 29 : 28;
+ case 3:
+ return 31;
+ case 4:
+ return 30;
+ case 5:
+ return 31;
+ case 6:
+ return 30;
+ case 7:
+ return 31;
+ case 8:
+ return 31;
+ case 9:
+ return 30;
+ case 10:
+ return 31;
+ case 11:
+ return 30;
+ case 12:
+ return 31;
+ default:
+ throw new IllegalArgumentException("Illegal month number: "
+ + monthNum);
+ }
+ }
+
+
+ private void readObject(java.io.ObjectInputStream stream)
+ throws java.io.IOException, ClassNotFoundException {
+
+ stream.defaultReadObject();
+ try {
+ buildExpression(cronExpression);
+ } catch (Exception ignore) {
+ } // never happens
+ }
+
+ @Override
+ @Deprecated
+ public Object clone() {
+ return new CronExpression(this);
+ }
+}
+
+class ValueSet {
+ public int value;
+
+ public int pos;
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
deleted file mode 100644
index 0dd3259..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/RemoteHttpJobBean.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.xxl.job.admin.core.jobbean;
-
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.quartz.JobKey;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.quartz.QuartzJobBean;
-
-/**
- * http job bean
- * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
- * @author xuxueli 2015-12-17 18:20:34
- */
-//@DisallowConcurrentExecution
-public class RemoteHttpJobBean extends QuartzJobBean {
- private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
-
- @Override
- protected void executeInternal(JobExecutionContext context)
- throws JobExecutionException {
-
- // load jobId
- JobKey jobKey = context.getTrigger().getJobKey();
- Integer jobId = Integer.valueOf(jobKey.getName());
-
- // trigger
- JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
- }
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
index 905fb86..1e4a74b 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobInfo.java
@@ -9,10 +9,10 @@
*/
public class XxlJobInfo {
- private int id; // 主键ID (JobKey.name)
+ private int id; // 主键ID
- private int jobGroup; // 执行器主键ID (JobKey.group)
- private String jobCron; // 任务执行CRON表达式 【base on quartz】
+ private int jobGroup; // 执行器主键ID
+ private String jobCron; // 任务执行CRON表达式
private String jobDesc;
private Date addTime;
@@ -34,9 +34,10 @@
private Date glueUpdatetime; // GLUE更新时间
private String childJobId; // 子任务ID,多个逗号分隔
-
- // copy from quartz
- private String jobStatus; // 任务状态 【base on quartz】
+
+ private int triggerStatus; // 调度状态:0-停止,1-运行
+ private long triggerLastTime; // 上次调度时间
+ private long triggerNextTime; // 下次调度时间
public int getId() {
@@ -191,12 +192,27 @@
this.childJobId = childJobId;
}
- public String getJobStatus() {
- return jobStatus;
+ public int getTriggerStatus() {
+ return triggerStatus;
}
- public void setJobStatus(String jobStatus) {
- this.jobStatus = jobStatus;
+ public void setTriggerStatus(int triggerStatus) {
+ this.triggerStatus = triggerStatus;
}
+ public long getTriggerLastTime() {
+ return triggerLastTime;
+ }
+
+ public void setTriggerLastTime(long triggerLastTime) {
+ this.triggerLastTime = triggerLastTime;
+ }
+
+ public long getTriggerNextTime() {
+ return triggerNextTime;
+ }
+
+ public void setTriggerNextTime(long triggerNextTime) {
+ this.triggerNextTime = triggerNextTime;
+ }
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
new file mode 100644
index 0000000..bd64011
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/RemoteHttpJobBean.java
@@ -0,0 +1,32 @@
+//package com.xxl.job.admin.core.jobbean;
+//
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+//import org.quartz.JobExecutionContext;
+//import org.quartz.JobExecutionException;
+//import org.quartz.JobKey;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.scheduling.quartz.QuartzJobBean;
+//
+///**
+// * http job bean
+// * “@DisallowConcurrentExecution” diable concurrent, thread size can not be only one, better given more
+// * @author xuxueli 2015-12-17 18:20:34
+// */
+////@DisallowConcurrentExecution
+//public class RemoteHttpJobBean extends QuartzJobBean {
+// private static Logger logger = LoggerFactory.getLogger(RemoteHttpJobBean.class);
+//
+// @Override
+// protected void executeInternal(JobExecutionContext context)
+// throws JobExecutionException {
+//
+// // load jobId
+// JobKey jobKey = context.getTrigger().getJobKey();
+// Integer jobId = Integer.valueOf(jobKey.getName());
+//
+//
+// }
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
new file mode 100644
index 0000000..1e62aa1
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobDynamicScheduler.java
@@ -0,0 +1,413 @@
+//package com.xxl.job.admin.core.schedule;
+//
+//import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+//import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
+//import com.xxl.job.admin.core.model.XxlJobInfo;
+//import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
+//import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
+//import com.xxl.job.admin.core.util.I18nUtil;
+//import com.xxl.job.core.biz.AdminBiz;
+//import com.xxl.job.core.biz.ExecutorBiz;
+//import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
+//import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
+//import com.xxl.rpc.remoting.invoker.call.CallType;
+//import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
+//import com.xxl.rpc.remoting.invoker.route.LoadBalance;
+//import com.xxl.rpc.remoting.net.NetEnum;
+//import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
+//import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
+//import com.xxl.rpc.serialize.Serializer;
+//import org.quartz.*;
+//import org.quartz.Trigger.TriggerState;
+//import org.quartz.impl.triggers.CronTriggerImpl;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//import org.springframework.util.Assert;
+//
+//import javax.servlet.ServletException;
+//import javax.servlet.http.HttpServletRequest;
+//import javax.servlet.http.HttpServletResponse;
+//import java.io.IOException;
+//import java.util.Date;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+///**
+// * base quartz scheduler util
+// * @author xuxueli 2015-12-19 16:13:53
+// */
+//public final class XxlJobDynamicScheduler {
+// private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler_old.class);
+//
+// // ---------------------- param ----------------------
+//
+// // scheduler
+// private static Scheduler scheduler;
+// public void setScheduler(Scheduler scheduler) {
+// XxlJobDynamicScheduler_old.scheduler = scheduler;
+// }
+//
+//
+// // ---------------------- init + destroy ----------------------
+// public void start() throws Exception {
+// // valid
+// Assert.notNull(scheduler, "quartz scheduler is null");
+//
+// // init i18n
+// initI18n();
+//
+// // admin registry monitor run
+// JobRegistryMonitorHelper.getInstance().start();
+//
+// // admin monitor run
+// JobFailMonitorHelper.getInstance().start();
+//
+// // admin-server
+// initRpcProvider();
+//
+// logger.info(">>>>>>>>> init xxl-job admin success.");
+// }
+//
+//
+// public void destroy() throws Exception {
+// // admin trigger pool stop
+// JobTriggerPoolHelper.toStop();
+//
+// // admin registry stop
+// JobRegistryMonitorHelper.getInstance().toStop();
+//
+// // admin monitor stop
+// JobFailMonitorHelper.getInstance().toStop();
+//
+// // admin-server
+// stopRpcProvider();
+// }
+//
+//
+// // ---------------------- I18n ----------------------
+//
+// private void initI18n(){
+// for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
+// item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
+// }
+// }
+//
+//
+// // ---------------------- admin rpc provider (no server version) ----------------------
+// private static ServletServerHandler servletServerHandler;
+// private void initRpcProvider(){
+// // init
+// XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
+// xxlRpcProviderFactory.initConfig(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// null,
+// 0,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null);
+//
+// // add services
+// xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
+//
+// // servlet handler
+// servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
+// }
+// private void stopRpcProvider() throws Exception {
+// XxlRpcInvokerFactory.getInstance().stop();
+// }
+// public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+// servletServerHandler.handle(null, request, response);
+// }
+//
+//
+// // ---------------------- executor-client ----------------------
+// private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
+// public static ExecutorBiz getExecutorBiz(String address) throws Exception {
+// // valid
+// if (address==null || address.trim().length()==0) {
+// return null;
+// }
+//
+// // load-cache
+// address = address.trim();
+// ExecutorBiz executorBiz = executorBizRepository.get(address);
+// if (executorBiz != null) {
+// return executorBiz;
+// }
+//
+// // set-cache
+// executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
+// NetEnum.NETTY_HTTP,
+// Serializer.SerializeEnum.HESSIAN.getSerializer(),
+// CallType.SYNC,
+// LoadBalance.ROUND,
+// ExecutorBiz.class,
+// null,
+// 5000,
+// address,
+// XxlJobAdminConfig.getAdminConfig().getAccessToken(),
+// null,
+// null).getObject();
+//
+// executorBizRepository.put(address, executorBiz);
+// return executorBiz;
+// }
+//
+//
+// // ---------------------- schedule util ----------------------
+//
+// /**
+// * fill job info
+// *
+// * @param jobInfo
+// */
+// public static void fillJobInfo(XxlJobInfo jobInfo) {
+//
+// String name = String.valueOf(jobInfo.getId());
+//
+// // trigger key
+// TriggerKey triggerKey = TriggerKey.triggerKey(name);
+// try {
+//
+// // trigger cron
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// if (trigger!=null && trigger instanceof CronTriggerImpl) {
+// String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
+// jobInfo.setJobCron(cronExpression);
+// }
+//
+// // trigger state
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// if (triggerState!=null) {
+// jobInfo.setJobStatus(triggerState.name());
+// }
+//
+// //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
+// //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// //String jobClass = jobDetail.getJobClass().getName();
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// }
+// }
+//
+//
+// /**
+// * add trigger + job
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// JobKey jobKey = new JobKey(jobName);
+//
+// // 2、valid
+// if (scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// // 3、corn trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
+// CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 4、job detail
+// Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
+// JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
+//
+// /*if (jobInfo.getJobData()!=null) {
+// JobDataMap jobDataMap = jobDetail.getJobDataMap();
+// jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
+// // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
+// }*/
+//
+// // 5、schedule job
+// Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
+//
+// logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
+// return true;
+// }
+//
+//
+// /**
+// * remove trigger + job
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean removeJob(String jobName) throws SchedulerException {
+//
+// JobKey jobKey = new JobKey(jobName);
+// scheduler.deleteJob(jobKey);
+//
+// /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.unscheduleJob(triggerKey); // trigger + job
+// }*/
+//
+// logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
+// return true;
+// }
+//
+//
+// /**
+// * updateJobCron
+// *
+// * @param jobName
+// * @param cronExpression
+// * @return
+// * @throws SchedulerException
+// */
+// public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
+//
+// // 1、job key
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// // 2、valid
+// if (!scheduler.checkExists(triggerKey)) {
+// return true; // PASS
+// }
+//
+// CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+//
+// // 3、avoid repeat cron
+// String oldCron = oldTrigger.getCronExpression();
+// if (oldCron.equals(cronExpression)){
+// return true; // PASS
+// }
+//
+// // 4、new cron trigger
+// CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
+// oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
+//
+// // 5、rescheduleJob
+// scheduler.rescheduleJob(triggerKey, oldTrigger);
+//
+// /*
+// JobKey jobKey = new JobKey(jobName);
+//
+// // old job detail
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+//
+// // new trigger
+// HashSet triggerSet = new HashSet();
+// triggerSet.add(cronTrigger);
+// // cover trigger of job detail
+// scheduler.scheduleJob(jobDetail, triggerSet, true);*/
+//
+// logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
+// return true;
+// }
+//
+//
+// /**
+// * pause
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean pauseJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.pauseTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * resume
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean resumeJob(String jobName) throws SchedulerException {
+//
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.resumeTrigger(triggerKey);
+// result = true;
+// }
+//
+// logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
+// return result;
+// }*/
+//
+//
+// /**
+// * run
+// *
+// * @param jobName
+// * @return
+// * @throws SchedulerException
+// */
+// /*public static boolean triggerJob(String jobName) throws SchedulerException {
+// // TriggerKey : name + group
+// JobKey jobKey = new JobKey(jobName);
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
+//
+// boolean result = false;
+// if (scheduler.checkExists(triggerKey)) {
+// scheduler.triggerJob(jobKey);
+// result = true;
+// logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
+// } else {
+// logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
+// }
+// return result;
+// }*/
+//
+//
+// /**
+// * finaAllJobList
+// *
+// * @return
+// *//*
+// @Deprecated
+// public static List> finaAllJobList(){
+// List> jobList = new ArrayList>();
+//
+// try {
+// if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
+// return null;
+// }
+// String groupName = scheduler.getJobGroupNames().get(0);
+// Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
+// if (jobKeys!=null && jobKeys.size()>0) {
+// for (JobKey jobKey : jobKeys) {
+// TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
+// Trigger trigger = scheduler.getTrigger(triggerKey);
+// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
+// TriggerState triggerState = scheduler.getTriggerState(triggerKey);
+// Map jobMap = new HashMap();
+// jobMap.put("TriggerKey", triggerKey);
+// jobMap.put("Trigger", trigger);
+// jobMap.put("JobDetail", jobDetail);
+// jobMap.put("TriggerState", triggerState);
+// jobList.add(jobMap);
+// }
+// }
+//
+// } catch (SchedulerException e) {
+// logger.error(e.getMessage(), e);
+// return null;
+// }
+// return jobList;
+// }*/
+//
+//}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
new file mode 100644
index 0000000..ad07430
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/old/XxlJobThreadPool.java
@@ -0,0 +1,58 @@
+//package com.xxl.job.admin.core.quartz;
+//
+//import org.quartz.SchedulerConfigException;
+//import org.quartz.spi.ThreadPool;
+//
+///**
+// * single thread pool, for async trigger
+// *
+// * @author xuxueli 2019-03-06
+// */
+//public class XxlJobThreadPool implements ThreadPool {
+//
+// @Override
+// public boolean runInThread(Runnable runnable) {
+//
+// // async run
+// runnable.run();
+// return true;
+//
+// //return false;
+// }
+//
+// @Override
+// public int blockForAvailableThreads() {
+// return 1;
+// }
+//
+// @Override
+// public void initialize() throws SchedulerConfigException {
+//
+// }
+//
+// @Override
+// public void shutdown(boolean waitForJobsToComplete) {
+//
+// }
+//
+// @Override
+// public int getPoolSize() {
+// return 1;
+// }
+//
+// @Override
+// public void setInstanceId(String schedInstId) {
+//
+// }
+//
+// @Override
+// public void setInstanceName(String schedName) {
+//
+// }
+//
+// // support
+// public void setThreadCount(int count) {
+// //
+// }
+//
+//}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
deleted file mode 100644
index 07d44f1..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/quartz/XxlJobThreadPool.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.xxl.job.admin.core.quartz;
-
-import org.quartz.SchedulerConfigException;
-import org.quartz.spi.ThreadPool;
-
-/**
- * single thread pool, for async trigger
- *
- * @author xuxueli 2019-03-06
- */
-public class XxlJobThreadPool implements ThreadPool {
-
- @Override
- public boolean runInThread(Runnable runnable) {
-
- // async run
- runnable.run();
- return true;
-
- //return false;
- }
-
- @Override
- public int blockForAvailableThreads() {
- return 1;
- }
-
- @Override
- public void initialize() throws SchedulerConfigException {
-
- }
-
- @Override
- public void shutdown(boolean waitForJobsToComplete) {
-
- }
-
- @Override
- public int getPoolSize() {
- return 1;
- }
-
- @Override
- public void setInstanceId(String schedInstId) {
-
- }
-
- @Override
- public void setInstanceName(String schedName) {
-
- }
-
- // support
- public void setThreadCount(int count) {
- //
- }
-
-}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
index 40d8373..992b4ce 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteBusyover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -21,7 +21,7 @@
// beat
ReturnT idleBeatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
idleBeatResult = executorBiz.idleBeat(triggerParam.getJobId());
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
index 6342809..26aaa06 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/route/strategy/ExecutorRouteFailover.java
@@ -1,7 +1,7 @@
package com.xxl.job.admin.core.route.strategy;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -22,7 +22,7 @@
// beat
ReturnT beatResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
beatResult = executorBiz.beat();
} catch (Exception e) {
logger.error(e.getMessage(), e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
deleted file mode 100644
index f18d9e4..0000000
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/schedule/XxlJobDynamicScheduler.java
+++ /dev/null
@@ -1,413 +0,0 @@
-package com.xxl.job.admin.core.schedule;
-
-import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
-import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
-import com.xxl.job.admin.core.model.XxlJobInfo;
-import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
-import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
-import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
-import com.xxl.job.admin.core.util.I18nUtil;
-import com.xxl.job.core.biz.AdminBiz;
-import com.xxl.job.core.biz.ExecutorBiz;
-import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
-import com.xxl.rpc.remoting.invoker.XxlRpcInvokerFactory;
-import com.xxl.rpc.remoting.invoker.call.CallType;
-import com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean;
-import com.xxl.rpc.remoting.invoker.route.LoadBalance;
-import com.xxl.rpc.remoting.net.NetEnum;
-import com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler;
-import com.xxl.rpc.remoting.provider.XxlRpcProviderFactory;
-import com.xxl.rpc.serialize.Serializer;
-import org.quartz.*;
-import org.quartz.Trigger.TriggerState;
-import org.quartz.impl.triggers.CronTriggerImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Date;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * base quartz scheduler util
- * @author xuxueli 2015-12-19 16:13:53
- */
-public final class XxlJobDynamicScheduler {
- private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);
-
- // ---------------------- param ----------------------
-
- // scheduler
- private static Scheduler scheduler;
- public void setScheduler(Scheduler scheduler) {
- XxlJobDynamicScheduler.scheduler = scheduler;
- }
-
-
- // ---------------------- init + destroy ----------------------
- public void start() throws Exception {
- // valid
- Assert.notNull(scheduler, "quartz scheduler is null");
-
- // init i18n
- initI18n();
-
- // admin registry monitor run
- JobRegistryMonitorHelper.getInstance().start();
-
- // admin monitor run
- JobFailMonitorHelper.getInstance().start();
-
- // admin-server
- initRpcProvider();
-
- logger.info(">>>>>>>>> init xxl-job admin success.");
- }
-
-
- public void destroy() throws Exception {
- // admin trigger pool stop
- JobTriggerPoolHelper.toStop();
-
- // admin registry stop
- JobRegistryMonitorHelper.getInstance().toStop();
-
- // admin monitor stop
- JobFailMonitorHelper.getInstance().toStop();
-
- // admin-server
- stopRpcProvider();
- }
-
-
- // ---------------------- I18n ----------------------
-
- private void initI18n(){
- for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
- item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
- }
- }
-
-
- // ---------------------- admin rpc provider (no server version) ----------------------
- private static ServletServerHandler servletServerHandler;
- private void initRpcProvider(){
- // init
- XxlRpcProviderFactory xxlRpcProviderFactory = new XxlRpcProviderFactory();
- xxlRpcProviderFactory.initConfig(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- null,
- 0,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null);
-
- // add services
- xxlRpcProviderFactory.addService(AdminBiz.class.getName(), null, XxlJobAdminConfig.getAdminConfig().getAdminBiz());
-
- // servlet handler
- servletServerHandler = new ServletServerHandler(xxlRpcProviderFactory);
- }
- private void stopRpcProvider() throws Exception {
- XxlRpcInvokerFactory.getInstance().stop();
- }
- public static void invokeAdminService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- servletServerHandler.handle(null, request, response);
- }
-
-
- // ---------------------- executor-client ----------------------
- private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
- public static ExecutorBiz getExecutorBiz(String address) throws Exception {
- // valid
- if (address==null || address.trim().length()==0) {
- return null;
- }
-
- // load-cache
- address = address.trim();
- ExecutorBiz executorBiz = executorBizRepository.get(address);
- if (executorBiz != null) {
- return executorBiz;
- }
-
- // set-cache
- executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(
- NetEnum.NETTY_HTTP,
- Serializer.SerializeEnum.HESSIAN.getSerializer(),
- CallType.SYNC,
- LoadBalance.ROUND,
- ExecutorBiz.class,
- null,
- 5000,
- address,
- XxlJobAdminConfig.getAdminConfig().getAccessToken(),
- null,
- null).getObject();
-
- executorBizRepository.put(address, executorBiz);
- return executorBiz;
- }
-
-
- // ---------------------- schedule util ----------------------
-
- /**
- * fill job info
- *
- * @param jobInfo
- */
- public static void fillJobInfo(XxlJobInfo jobInfo) {
-
- String name = String.valueOf(jobInfo.getId());
-
- // trigger key
- TriggerKey triggerKey = TriggerKey.triggerKey(name);
- try {
-
- // trigger cron
- Trigger trigger = scheduler.getTrigger(triggerKey);
- if (trigger!=null && trigger instanceof CronTriggerImpl) {
- String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
- jobInfo.setJobCron(cronExpression);
- }
-
- // trigger state
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- if (triggerState!=null) {
- jobInfo.setJobStatus(triggerState.name());
- }
-
- //JobKey jobKey = new JobKey(jobInfo.getJobName(), String.valueOf(jobInfo.getJobGroup()));
- //JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- //String jobClass = jobDetail.getJobClass().getName();
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- }
- }
-
-
- /**
- * add trigger + job
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean addJob(String jobName, String cronExpression) throws SchedulerException {
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- JobKey jobKey = new JobKey(jobName);
-
- // 2、valid
- if (scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- // 3、corn trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
- CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 4、job detail
- Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
- JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
-
- /*if (jobInfo.getJobData()!=null) {
- JobDataMap jobDataMap = jobDetail.getJobDataMap();
- jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class));
- // JobExecutionContext context.getMergedJobDataMap().get("mailGuid");
- }*/
-
- // 5、schedule job
- Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
-
- logger.info(">>>>>>>>>>> addJob success(quartz), jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
- return true;
- }
-
-
- /**
- * remove trigger + job
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- public static boolean removeJob(String jobName) throws SchedulerException {
-
- JobKey jobKey = new JobKey(jobName);
- scheduler.deleteJob(jobKey);
-
- /*TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
- if (scheduler.checkExists(triggerKey)) {
- scheduler.unscheduleJob(triggerKey); // trigger + job
- }*/
-
- logger.info(">>>>>>>>>>> removeJob success(quartz), jobKey:{}", jobKey);
- return true;
- }
-
-
- /**
- * updateJobCron
- *
- * @param jobName
- * @param cronExpression
- * @return
- * @throws SchedulerException
- */
- public static boolean updateJobCron(String jobName, String cronExpression) throws SchedulerException {
-
- // 1、job key
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- // 2、valid
- if (!scheduler.checkExists(triggerKey)) {
- return true; // PASS
- }
-
- CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
- // 3、avoid repeat cron
- String oldCron = oldTrigger.getCronExpression();
- if (oldCron.equals(cronExpression)){
- return true; // PASS
- }
-
- // 4、new cron trigger
- CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
- oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
-
- // 5、rescheduleJob
- scheduler.rescheduleJob(triggerKey, oldTrigger);
-
- /*
- JobKey jobKey = new JobKey(jobName);
-
- // old job detail
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
-
- // new trigger
- HashSet triggerSet = new HashSet();
- triggerSet.add(cronTrigger);
- // cover trigger of job detail
- scheduler.scheduleJob(jobDetail, triggerSet, true);*/
-
- logger.info(">>>>>>>>>>> resumeJob success, JobName:{}", jobName);
- return true;
- }
-
-
- /**
- * pause
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean pauseJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.pauseTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> pauseJob {}, triggerKey:{}", (result?"success":"fail"),triggerKey);
- return result;
- }*/
-
-
- /**
- * resume
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean resumeJob(String jobName) throws SchedulerException {
-
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.resumeTrigger(triggerKey);
- result = true;
- }
-
- logger.info(">>>>>>>>>>> resumeJob {}, triggerKey:{}", (result?"success":"fail"), triggerKey);
- return result;
- }*/
-
-
- /**
- * run
- *
- * @param jobName
- * @return
- * @throws SchedulerException
- */
- /*public static boolean triggerJob(String jobName) throws SchedulerException {
- // TriggerKey : name + group
- JobKey jobKey = new JobKey(jobName);
- TriggerKey triggerKey = TriggerKey.triggerKey(jobName);
-
- boolean result = false;
- if (scheduler.checkExists(triggerKey)) {
- scheduler.triggerJob(jobKey);
- result = true;
- logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
- } else {
- logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
- }
- return result;
- }*/
-
-
- /**
- * finaAllJobList
- *
- * @return
- *//*
- @Deprecated
- public static List> finaAllJobList(){
- List> jobList = new ArrayList>();
-
- try {
- if (scheduler.getJobGroupNames()==null || scheduler.getJobGroupNames().size()==0) {
- return null;
- }
- String groupName = scheduler.getJobGroupNames().get(0);
- Set jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
- if (jobKeys!=null && jobKeys.size()>0) {
- for (JobKey jobKey : jobKeys) {
- TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(), Scheduler.DEFAULT_GROUP);
- Trigger trigger = scheduler.getTrigger(triggerKey);
- JobDetail jobDetail = scheduler.getJobDetail(jobKey);
- TriggerState triggerState = scheduler.getTriggerState(triggerKey);
- Map jobMap = new HashMap();
- jobMap.put("TriggerKey", triggerKey);
- jobMap.put("Trigger", trigger);
- jobMap.put("JobDetail", jobDetail);
- jobMap.put("TriggerState", triggerState);
- jobList.add(jobMap);
- }
- }
-
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return null;
- }
- return jobList;
- }*/
-
-}
\ No newline at end of file
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
new file mode 100644
index 0000000..cded7d5
--- /dev/null
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobScheduleHelper.java
@@ -0,0 +1,218 @@
+package com.xxl.job.admin.core.thread;
+
+import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
+import com.xxl.job.admin.core.trigger.TriggerTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xuxueli 2019-05-21
+ */
+public class JobScheduleHelper {
+ private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
+
+ private static JobScheduleHelper instance = new JobScheduleHelper();
+ public static JobScheduleHelper getInstance(){
+ return instance;
+ }
+
+ private Thread scheduleThread;
+ private Thread ringThread;
+ private volatile boolean toStop = false;
+ private volatile static Map> ringData = new ConcurrentHashMap<>();
+
+ public void start(){
+
+ // schedule thread
+ scheduleThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (!toStop) {
+ // 随机休眠1s内
+ try {
+ TimeUnit.MILLISECONDS.sleep(500+new Random().nextInt(500));
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // 匹配任务
+ Connection conn = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ if (conn==null || conn.isClosed()) {
+ conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
+ }
+ conn.setAutoCommit(false);
+
+ preparedStatement = conn.prepareStatement( "select * from XXL_JOB_LOCK where lock_name = 'schedule_lock' for update" );
+ preparedStatement.execute();
+
+ // tx start
+
+ // 1、查询JOB:"下次调度30s内" ( ...... -5 ... now ...... +30 ...... )
+ long maxNextTime = System.currentTimeMillis() + 30000;
+ long nowTime = System.currentTimeMillis();
+ List scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(maxNextTime);
+ if (scheduleList!=null && scheduleList.size()>0) {
+ // 2、推送时间轮
+ for (XxlJobInfo jobInfo: scheduleList) {
+
+ // 过期策略:过期=更新下次触发时间为当前、过期是否5s内=立即出发,否则忽略;
+ if (jobInfo.getTriggerNextTime() < nowTime) {
+ jobInfo.setTriggerNextTime(nowTime);
+ if (jobInfo.getTriggerNextTime() < nowTime-10000) {
+ continue;
+ }
+ }
+
+ // push async ring
+ int second = (int)((jobInfo.getTriggerNextTime()/1000)%60);
+ List ringItemData = ringData.get(second);
+ if (ringItemData == null) {
+ ringItemData = new ArrayList();
+ ringData.put(second, ringItemData);
+ }
+ ringItemData.add(jobInfo.getId());
+
+ logger.info(">>>>>>>>>>> xxl-job, push time-ring : " + second + " = " + Arrays.asList(ringItemData) );
+ }
+
+ // 3、更新trigger信息
+ for (XxlJobInfo jobInfo: scheduleList) {
+ // update
+ jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
+ jobInfo.setTriggerNextTime(
+ new CronExpression(jobInfo.getJobCron())
+ .getNextValidTimeAfter(new Date(jobInfo.getTriggerNextTime()))
+ .getTime()
+ );
+ XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
+ }
+
+ }
+
+ // tx stop
+
+ conn.commit();
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
+ }
+ } finally {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (null != preparedStatement) {
+ try {
+ preparedStatement.close();
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
+ }
+ });
+ scheduleThread.setDaemon(true);
+ scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
+ scheduleThread.start();
+
+
+ // ring thread
+ ringThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int lastSecond = -1;
+ while (!toStop) {
+ try {
+ // second data
+ List ringItemData = new ArrayList<>();
+ int nowSecond = (int)((System.currentTimeMillis()/1000)%60); // 避免处理耗时太长,跨过刻度;
+ if (lastSecond == -1) {
+ if (ringData.containsKey(nowSecond)) {
+ List tmpData = ringData.remove(nowSecond);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+ }
+ lastSecond = nowSecond;
+ } else {
+ for (int i = 1; i <=60; i++) {
+ int secondItem = (lastSecond+i)%60;
+
+ List tmpData = ringData.remove(secondItem);
+ if (tmpData != null) {
+ ringItemData.addAll(tmpData);
+ }
+
+ if (secondItem == nowSecond) {
+ break;
+ }
+ }
+ lastSecond = nowSecond;
+ }
+
+ logger.info(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
+ if (ringItemData!=null && ringItemData.size()>0) {
+ // do trigger
+ for (int jobId: ringItemData) {
+ // do trigger
+ JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
+ }
+
+ // clear
+ ringItemData.clear();
+ }
+ } catch (Exception e) {
+ if (!toStop) {
+ logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
+ }
+ }
+
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
+ }
+ });
+ ringThread.setDaemon(true);
+ ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
+ ringThread.start();
+ }
+
+ public void toStop(){
+ toStop = true;
+
+ // interrupt and wait
+ scheduleThread.interrupt();
+ try {
+ scheduleThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+
+ // interrupt and wait
+ ringThread.interrupt();
+ try {
+ ringThread.join();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
index 9dd6308..7b3971c 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java
@@ -1,11 +1,11 @@
package com.xxl.job.admin.core.trigger;
import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
+import com.xxl.job.admin.core.conf.XxlJobScheduler;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
@@ -192,7 +192,7 @@
public static ReturnT runExecutor(TriggerParam triggerParam, String address){
ReturnT runResult = null;
try {
- ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
+ ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
index 338ab43..3cb89ad 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/dao/XxlJobInfoDao.java
@@ -29,7 +29,7 @@
public XxlJobInfo loadById(@Param("id") int id);
- public int update(XxlJobInfo item);
+ public int update(XxlJobInfo xxlJobInfo);
public int delete(@Param("id") int id);
@@ -37,4 +37,9 @@
public int findAllCount();
+ public List scheduleJobQuery(@Param("maxNextTime") long maxNextTime);
+
+ public int scheduleUpdate(XxlJobInfo xxlJobInfo);
+
+
}
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
index fbcc360..e8ef432 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java
@@ -28,7 +28,7 @@
public Map pageList(int start, int length, int jobGroup, String jobDesc, String executorHandler, String filterTime);
/**
- * add job, default quartz stop
+ * add job
*
* @param jobInfo
* @return
@@ -36,7 +36,7 @@
public ReturnT add(XxlJobInfo jobInfo);
/**
- * update job, update quartz-cron if started
+ * update job
*
* @param jobInfo
* @return
@@ -44,15 +44,15 @@
public ReturnT update(XxlJobInfo jobInfo);
/**
- * remove job, unbind quartz
- *
+ * remove job
+ * *
* @param id
* @return
*/
public ReturnT remove(int id);
/**
- * start job, bind quartz
+ * start job
*
* @param id
* @return
@@ -60,7 +60,7 @@
public ReturnT start(int id);
/**
- * stop job, unbind quartz
+ * stop job
*
* @param id
* @return
diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
index 0edab35..ba40bd8 100644
--- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
+++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java
@@ -2,8 +2,8 @@
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
+import com.xxl.job.admin.core.cron.CronExpression;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
-import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
@@ -14,14 +14,13 @@
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.util.DateUtil;
-import org.quartz.CronExpression;
-import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
+import java.text.ParseException;
import java.util.*;
/**
@@ -48,13 +47,6 @@
List list = xxlJobInfoDao.pageList(start, length, jobGroup, jobDesc, executorHandler);
int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, jobDesc, executorHandler);
- // fill job info
- if (list!=null && list.size()>0) {
- for (XxlJobInfo jobInfo : list) {
- XxlJobDynamicScheduler.fillJobInfo(jobInfo);
- }
- }
-
// package result
Map maps = new HashMap();
maps.put("recordsTotal", list_count); // 总记录数
@@ -197,6 +189,15 @@
return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
}
+ // next trigger time
+ long nextTriggerTime = 0;
+ try {
+ nextTriggerTime = new CronExpression(jobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
+ logger.error(e.getMessage(), e);
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
+ }
+
exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
exists_jobInfo.setJobCron(jobInfo.getJobCron());
exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
@@ -209,18 +210,10 @@
exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
+ exists_jobInfo.setTriggerNextTime(nextTriggerTime);
xxlJobInfoDao.update(exists_jobInfo);
- // update quartz-cron if started
- try {
- String qz_name = String.valueOf(exists_jobInfo.getId());
- XxlJobDynamicScheduler.updateJobCron(qz_name, exists_jobInfo.getJobCron());
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
return ReturnT.SUCCESS;
}
@@ -230,76 +223,54 @@
if (xxlJobInfo == null) {
return ReturnT.SUCCESS;
}
- String name = String.valueOf(xxlJobInfo.getId());
- try {
- // unbind quartz
- XxlJobDynamicScheduler.removeJob(name);
-
- xxlJobInfoDao.delete(id);
- xxlJobLogDao.delete(id);
- xxlJobLogGlueDao.deleteByJobId(id);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
- }
-
+ xxlJobInfoDao.delete(id);
+ xxlJobLogDao.delete(id);
+ xxlJobLogGlueDao.deleteByJobId(id);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT start(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
- String cronExpression = xxlJobInfo.getJobCron();
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- boolean ret = XxlJobDynamicScheduler.addJob(name, cronExpression);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
+
+ xxlJobInfo.setTriggerStatus(1);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
+
+ xxlJobInfoDao.update(xxlJobInfo);
+ return ReturnT.SUCCESS;
}
@Override
public ReturnT stop(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- String name = String.valueOf(xxlJobInfo.getId());
+ // next trigger time
+ long nextTriggerTime = 0;
try {
- // bind quartz
- boolean ret = XxlJobDynamicScheduler.removeJob(name);
- return ret?ReturnT.SUCCESS:ReturnT.FAIL;
- } catch (SchedulerException e) {
+ nextTriggerTime = new CronExpression(xxlJobInfo.getJobCron()).getNextValidTimeAfter(new Date()).getTime();
+ } catch (ParseException e) {
logger.error(e.getMessage(), e);
- return ReturnT.FAIL;
+ return new ReturnT(ReturnT.FAIL_CODE, I18nUtil.getString("jobinfo_field_cron_unvalid")+" | "+ e.getMessage());
}
- }
- /*@Override
- public ReturnT triggerJob(int id, int failRetryCount) {
+ xxlJobInfo.setTriggerStatus(0);
+ xxlJobInfo.setTriggerLastTime(0);
+ xxlJobInfo.setTriggerNextTime(nextTriggerTime);
- JobTriggerPoolHelper.trigger(id, failRetryCount);
+ xxlJobInfoDao.update(xxlJobInfo);
return ReturnT.SUCCESS;
-
- *//*XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
- if (xxlJobInfo == null) {
- return new ReturnT(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_unvalid")) );
- }
-
- String group = String.valueOf(xxlJobInfo.getJobGroup());
- String name = String.valueOf(xxlJobInfo.getId());
-
- try {
- XxlJobDynamicScheduler.triggerJob(name, group);
- return ReturnT.SUCCESS;
- } catch (SchedulerException e) {
- logger.error(e.getMessage(), e);
- return new ReturnT(ReturnT.FAIL_CODE, e.getMessage());
- }*//*
-
- }*/
+ }
@Override
public Map dashboardInfo() {
diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
index 78881a3..f8ff78e 100644
--- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
+++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobGroupMapper.xml
@@ -23,24 +23,24 @@
- INSERT INTO XXL_JOB_QRTZ_TRIGGER_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
+ INSERT INTO XXL_JOB_GROUP ( `app_name`, `title`, `order`, `address_type`, `address_list`)
values ( #{appName}, #{title}, #{order}, #{addressType}, #{addressList});
- UPDATE XXL_JOB_QRTZ_TRIGGER_GROUP
+ UPDATE XXL_JOB_GROUP
SET `app_name` = #{appName},
`title` = #{title},
`order` = #{order},
@@ -50,13 +50,13 @@
- DELETE FROM XXL_JOB_QRTZ_TRIGGER_GROUP
+ DELETE FROM XXL_JOB_GROUP
WHERE id = #{id}
diff --git a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
index 3b3b9ce..3380b25 100644
--- a/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
+++ b/xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml
@@ -29,6 +29,10 @@
+
+
+
+
@@ -50,12 +54,15 @@
t.glue_source,
t.glue_remark,
t.glue_updatetime,
- t.child_jobid
+ t.child_jobid,
+ t.trigger_status,
+ t.trigger_last_time,
+ t.trigger_next_time