четверг, 31 октября 2013 г.

Qurtz - getTimeBefore();

This post is related to known problem with missing (not implemented method) getTimeBefore();
 Method by default return null.
The key issue was in getting previous run time when quartz-task hasn't yet been run.

When we need to get previous fire time - there is no way to do this in standard way for the first-time quartz task run.
Method is related to some kind of 'brute force' technique, it could be used once, on first-time run and then you could use standard method to retrieve previous time. The idea of method is to work with cron expression directly by sorting out values.
This will NOT work for the first time-run:

Date lastFiredJobTime = jobExecutionContext.getPreviousFireTime();

The way to achive this is to get scheduled fire time first:
Date scheduledCurrentJobTime = jobExecutionContext.getScheduledFireTime();

Then, when we have scheduled fire time, we can make a simple workaround for not implemented method to get previous fire-time:
public class CustomCronExpression extends CronExpression {
    private static final String DELIMITER = " ";
 /**
  * Constructs a new <CODE>CronExpression</CODE> 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
  *         <CODE>CronExpression</CODE>
  */

  public CustomCronExpression(String cronExpression) throws ParseException {
   super(cronExpression);
  }

  // here we can override this method and call it in future for the first-time 
  // lauch to get previous time
    @Override
    public Date getTimeBefore(Date targetDate) {
        Date nextFireTime = getTimeAfter(targetDate);

    Calendar calendar = Calendar.getInstance(getTimeZone());
    calendar.setTime(nextFireTime);
    calendar.add(Calendar.SECOND, -1);

    int dateUnit = getDateUnitCronExpression();

  switch (dateUnit) {
      case -1:
          break;
      case MINUTE:
          calendar.add(Calendar.MINUTE, -1);
          break;
      case HOUR:
       calendar.add(Calendar.HOUR_OF_DAY, -1);
       break;
      case DAY_OF_MONTH:
      case DAY_OF_WEEK:
       calendar.add(Calendar.DAY_OF_YEAR, -1);
       break;
      case MONTH:
       calendar.add(Calendar.MONTH, -1);
       break;
      default:
       calendar.add(Calendar.YEAR, -1);
       break;
    }

    Date previousDate = getTimeAfter(calendar.getTime());
    Date afterPreviousDate = getTimeAfter(previousDate);
    return findPreviousJobFireDate(afterPreviousDate, targetDate);
  }

  private int getDateUnitCronExpression() {
    String cronExpression = getCronExpression();
    if (StringUtils.isEmpty(cronExpression)) {
      return -1;
    }

    String[] expression = cronExpression.split(DELIMITER);
    return findDateUnitToWorkWith(expression);
  }

  private Date findPreviousJobFireDate(Date afterFuturePreviousDate, Date targetDate) {
    Date futureDate = afterFuturePreviousDate;
    Date resultDate = targetDate;

    while (true) {
      if (futureDate.equals(targetDate)) {
       return resultDate;
      } else {
       resultDate = futureDate;
       futureDate = getTimeAfter(resultDate);
       // prevent NPE and return current job time
       if(futureDate == null) {
         return resultDate;
       }
      }
    }
  }

  private int findDateUnitToWorkWith(String[] expression) {
   // * - represent the unit of date in cron expression
    // [0]Sec [1]Min [2]Hour [3]DayOfMonth [4]Month [5]DayOfWeek [6]Year
    for (int i = 0, n = expression.length; i < n; i++) {
      if (expression[i].equals("*")) {
       return i;
      }
    }
    return YEAR;
  }

}



For example, we have next CronExpression like: 0 15 10 * * ? 
findDateUnitToWorkWith(String[] expression)  - this method will find first time * in cron expression. When we find it - first we should get next fire time and then substract one unit of date from next fire time. This way we'll have 'time in the past' (if we get a * on day, like in our example, then we should substract one day and start bruteforcing to our next fire time. When we reach it - take previous, resultDate, time and return it. )

Thanks!

1 комментарий: