From 2273c26793fe6e3abfd90a400823e8e46b3303bb Mon Sep 17 00:00:00 2001
From: abobrov <abobrov@localhost>
Date: Mon, 15 Dec 2008 16:07:29 +0000
Subject: [PATCH] - [Issue 274]  Recurring Tasks

---
 opends/src/server/org/opends/server/backends/task/RecurringTask.java |  406 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 381 insertions(+), 25 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/task/RecurringTask.java b/opends/src/server/org/opends/server/backends/task/RecurringTask.java
index ab117ae..67fcec9 100644
--- a/opends/src/server/org/opends/server/backends/task/RecurringTask.java
+++ b/opends/src/server/org/opends/server/backends/task/RecurringTask.java
@@ -25,6 +25,10 @@
  *      Copyright 2006-2008 Sun Microsystems, Inc.
  */
 package org.opends.server.backends.task;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.GregorianCalendar;
 import org.opends.messages.Message;
 
 
@@ -32,6 +36,8 @@
 import java.util.Iterator;
 import java.util.List;
 
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -45,9 +51,13 @@
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.Attributes;
 import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.RDN;
+
 import static org.opends.messages.BackendMessages.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.ServerConstants.*;
 
 
 
@@ -78,7 +88,38 @@
   // class.
   private String taskClassName;
 
+  // Task instance.
+  private Task task;
 
+  // Task scheduler for this task.
+  private TaskScheduler taskScheduler;
+
+  // Number of tokens in the task schedule tab.
+  private static final int TASKTAB_NUM_TOKENS = 5;
+
+  /**
+   * Task tab fields.
+   */
+  private static enum TaskTab {MINUTE, HOUR, DAY, MONTH, WEEKDAY};
+
+  // Exact match pattern.
+  private static final Pattern exactPattern =
+    Pattern.compile("\\d+");
+
+  // Range match pattern.
+  private static final Pattern rangePattern =
+    Pattern.compile("\\d+[-]\\d+");
+
+  // List match pattern.
+  private static final Pattern listPattern =
+    Pattern.compile("^(\\d+,)(.*)(\\d+)$");
+
+  // Boolean arrays holding task tab slots.
+  private boolean[] minutesArray;
+  private boolean[] hoursArray;
+  private boolean[] daysArray;
+  private boolean[] monthArray;
+  private boolean[] weekdayArray;
 
   /**
    * Creates a new recurring task based on the information in the provided
@@ -95,10 +136,10 @@
   public RecurringTask(TaskScheduler taskScheduler, Entry recurringTaskEntry)
          throws DirectoryException
   {
-    this.recurringTaskEntry   = recurringTaskEntry;
+    this.taskScheduler = taskScheduler;
+    this.recurringTaskEntry = recurringTaskEntry;
     this.recurringTaskEntryDN = recurringTaskEntry.getDN();
 
-
     // Get the recurring task ID from the entry.  If there isn't one, then fail.
     AttributeType attrType = DirectoryServer.getAttributeType(
                                   ATTR_RECURRING_TASK_ID.toLowerCase());
@@ -142,39 +183,35 @@
     recurringTaskID = value.getStringValue();
 
 
-    // FIXME -- Need to have some method of getting the scheduling information
-    //          from the recurring task entry.
-
-
-    // Get the class name from the entry.  If there isn't one, then fail.
+    // Get the schedule for this task.
     attrType = DirectoryServer.getAttributeType(
-                    ATTR_RECURRING_TASK_CLASS_NAME.toLowerCase());
+                    ATTR_RECURRING_TASK_SCHEDULE.toLowerCase());
     if (attrType == null)
     {
       attrType = DirectoryServer.getDefaultAttributeType(
-                                      ATTR_RECURRING_TASK_CLASS_NAME);
+        ATTR_RECURRING_TASK_SCHEDULE);
     }
 
     attrList = recurringTaskEntry.getAttribute(attrType);
     if ((attrList == null) || attrList.isEmpty())
     {
-      Message message = ERR_RECURRINGTASK_NO_CLASS_ATTRIBUTE.get(
-          ATTR_RECURRING_TASK_CLASS_NAME);
+      Message message = ERR_RECURRINGTASK_NO_SCHEDULE_ATTRIBUTE.get(
+          ATTR_RECURRING_TASK_SCHEDULE);
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
     }
 
-    if (attrList.size() > 0)
+    if (attrList.size() > 1)
     {
-      Message message = ERR_RECURRINGTASK_MULTIPLE_CLASS_TYPES.get(
-          ATTR_RECURRING_TASK_CLASS_NAME);
+      Message message = ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_TYPES.get(
+          ATTR_RECURRING_TASK_SCHEDULE);
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
     }
 
     attr = attrList.get(0);
     if (attr.isEmpty())
     {
-      Message message =
-          ERR_RECURRINGTASK_NO_CLASS_VALUES.get(ATTR_RECURRING_TASK_CLASS_NAME);
+      Message message = ERR_RECURRINGTASK_NO_SCHEDULE_VALUES.get(
+        ATTR_RECURRING_TASK_SCHEDULE);
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
     }
 
@@ -182,8 +219,51 @@
     value = iterator.next();
     if (iterator.hasNext())
     {
-      Message message = ERR_RECURRINGTASK_MULTIPLE_CLASS_VALUES.get(
-          ATTR_RECURRING_TASK_CLASS_NAME);
+      Message message = ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_VALUES.get(
+          ATTR_RECURRING_TASK_SCHEDULE);
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
+    }
+
+    String taskScheduleTab = value.getStringValue();
+    parseTaskTab(taskScheduleTab);
+
+    // Get the class name from the entry.  If there isn't one, then fail.
+    attrType = DirectoryServer.getAttributeType(
+                    ATTR_TASK_CLASS.toLowerCase());
+    if (attrType == null)
+    {
+      attrType = DirectoryServer.getDefaultAttributeType(ATTR_TASK_CLASS);
+    }
+
+    attrList = recurringTaskEntry.getAttribute(attrType);
+    if ((attrList == null) || attrList.isEmpty())
+    {
+      Message message = ERR_TASKSCHED_NO_CLASS_ATTRIBUTE.get(
+          ATTR_TASK_CLASS);
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
+    }
+
+    if (attrList.size() > 1)
+    {
+      Message message = ERR_TASKSCHED_MULTIPLE_CLASS_TYPES.get(
+          ATTR_TASK_CLASS);
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
+    }
+
+    attr = attrList.get(0);
+    if (attr.isEmpty())
+    {
+      Message message =
+          ERR_TASKSCHED_NO_CLASS_VALUES.get(ATTR_TASK_CLASS);
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
+    }
+
+    iterator = attr.iterator();
+    value = iterator.next();
+    if (iterator.hasNext())
+    {
+      Message message = ERR_TASKSCHED_MULTIPLE_CLASS_VALUES.get(
+          ATTR_TASK_CLASS);
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
     }
 
@@ -204,7 +284,7 @@
       }
 
       Message message = ERR_RECURRINGTASK_CANNOT_LOAD_CLASS.
-          get(String.valueOf(taskClassName), ATTR_RECURRING_TASK_CLASS_NAME,
+          get(String.valueOf(taskClassName), ATTR_TASK_CLASS,
               getExceptionMessage(e));
       throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message,
                                    e);
@@ -212,7 +292,6 @@
 
 
     // Make sure that the specified class can be instantiated as a task.
-    Task task;
     try
     {
       task = (Task) taskClass.newInstance();
@@ -308,12 +387,289 @@
   /**
    * Schedules the next iteration of this recurring task for processing.
    *
-   * @return  The task that has been scheduled for processing.
+   * @return The task that has been scheduled for processing.
+   * @throws DirectoryException to indicate an error.
    */
-  public Task scheduleNextIteration()
+  public Task scheduleNextIteration() throws DirectoryException
   {
-    // NYI
-    return null;
+    Task nextTask = null;
+    Date nextTaskDate = null;
+
+    try {
+      nextTaskDate = getNextIteration();
+    } catch (IllegalArgumentException e) {
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+        ERR_RECURRINGTASK_INVALID_TOKENS_COMBO.get(
+        ATTR_RECURRING_TASK_SCHEDULE));
+    }
+
+    SimpleDateFormat dateFormat = new SimpleDateFormat(
+      DATE_FORMAT_COMPACT_LOCAL_TIME);
+    String nextTaskStartTime = dateFormat.format(nextTaskDate);
+
+    try {
+      // Make a regular task iteration from this recurring task.
+      nextTask = task.getClass().newInstance();
+      Entry nextTaskEntry = recurringTaskEntry.duplicate(false);
+      String nextTaskID = task.getTaskID() + " - " +
+        nextTaskDate.toString();
+      String nextTaskIDName = NAME_PREFIX_TASK + "id";
+      AttributeType taskIDAttrType =
+        DirectoryServer.getAttributeType(nextTaskIDName);
+      Attribute nextTaskIDAttr = Attributes.create(
+        taskIDAttrType, nextTaskID);
+      nextTaskEntry.replaceAttribute(nextTaskIDAttr);
+      RDN nextTaskRDN = RDN.decode(nextTaskIDName + "=" + nextTaskID);
+      DN nextTaskDN = new DN(nextTaskRDN,
+        taskScheduler.getTaskBackend().getScheduledTasksParentDN());
+      nextTaskEntry.setDN(nextTaskDN);
+
+      String nextTaskStartTimeName = NAME_PREFIX_TASK +
+        "scheduled-start-time";
+      AttributeType taskStartTimeAttrType =
+        DirectoryServer.getAttributeType(nextTaskStartTimeName);
+      Attribute nextTaskStartTimeAttr = Attributes.create(
+        taskStartTimeAttrType, nextTaskStartTime);
+      nextTaskEntry.replaceAttribute(nextTaskStartTimeAttr);
+
+      nextTask.initializeTaskInternal(taskScheduler, nextTaskEntry);
+      nextTask.initializeTask();
+    } catch (Exception e) {
+      // Should not happen, debug log it otherwise.
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+
+    return nextTask;
+  }
+
+  /**
+   * Parse and validate recurring task schedule.
+   * @param taskSchedule recurring task schedule tab in crontab(5) format.
+   * @throws DirectoryException to indicate an error.
+   */
+  private void parseTaskTab(String taskSchedule) throws DirectoryException
+  {
+    StringTokenizer st = new StringTokenizer(taskSchedule);
+
+    if (st.countTokens() != TASKTAB_NUM_TOKENS) {
+      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+        ERR_RECURRINGTASK_INVALID_N_TOKENS.get(
+        ATTR_RECURRING_TASK_SCHEDULE));
+    }
+
+    for (TaskTab taskTabToken : TaskTab.values()) {
+      String token = st.nextToken();
+      switch (taskTabToken) {
+        case MINUTE:
+          try {
+            minutesArray = parseTaskTabField(token, 0, 59);
+          } catch (IllegalArgumentException e) {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN.get(
+              ATTR_RECURRING_TASK_SCHEDULE));
+          }
+          break;
+        case HOUR:
+          try {
+            hoursArray = parseTaskTabField(token, 0, 23);
+          } catch (IllegalArgumentException e) {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_RECURRINGTASK_INVALID_HOUR_TOKEN.get(
+              ATTR_RECURRING_TASK_SCHEDULE));
+          }
+          break;
+        case DAY:
+          try {
+            daysArray = parseTaskTabField(token, 1, 31);
+          } catch (IllegalArgumentException e) {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_RECURRINGTASK_INVALID_DAY_TOKEN.get(
+              ATTR_RECURRING_TASK_SCHEDULE));
+          }
+          break;
+        case MONTH:
+          try {
+            monthArray = parseTaskTabField(token, 1, 12);
+          } catch (IllegalArgumentException e) {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_RECURRINGTASK_INVALID_MONTH_TOKEN.get(
+              ATTR_RECURRING_TASK_SCHEDULE));
+          }
+          break;
+        case WEEKDAY:
+          try {
+            weekdayArray = parseTaskTabField(token, 0, 6);
+          } catch (IllegalArgumentException e) {
+            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
+              ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN.get(
+              ATTR_RECURRING_TASK_SCHEDULE));
+          }
+          break;
+      }
+    }
+  }
+
+  /**
+   * Parse and validate recurring task schedule field.
+   * @param tabField recurring task schedule field in crontab(5) format.
+   * @param minValue minimum value allowed for this field.
+   * @param maxValue maximum value allowed for this field.
+   * @return boolean schedule slots range set according to
+   *         the schedule field.
+   * @throws IllegalArgumentException if tab field is invalid.
+   */
+  private boolean[] parseTaskTabField(String tabField,
+    int minValue, int maxValue) throws IllegalArgumentException
+  {
+    boolean[] valueList = new boolean[maxValue + 1];
+    Arrays.fill(valueList, false);
+
+    // Blanket.
+    if (tabField.equals("*")) {
+      for (int i = minValue; i <= maxValue; i++) {
+        valueList[i] = true;
+      }
+      return valueList;
+    }
+
+    // Exact.
+    if (exactPattern.matcher(tabField).matches()) {
+      int value = Integer.parseInt(tabField);
+      if ((value >= minValue) && (value <= maxValue)) {
+        valueList[value] = true;
+        return valueList;
+      }
+      throw new IllegalArgumentException();
+    }
+
+    // Range.
+    if (rangePattern.matcher(tabField).matches()) {
+      StringTokenizer st = new StringTokenizer(tabField, "-");
+      int startValue = Integer.parseInt(st.nextToken());
+      int endValue = Integer.parseInt(st.nextToken());
+      if ((startValue < endValue) &&
+          ((startValue >= minValue) && (endValue <= maxValue)))
+      {
+        for (int i = startValue; i <= endValue; i++) {
+          valueList[i] = true;
+        }
+        return valueList;
+      }
+      throw new IllegalArgumentException();
+    }
+
+    // List.
+    if (listPattern.matcher(tabField).matches()) {
+      StringTokenizer st = new StringTokenizer(tabField, ",");
+      while (st.hasMoreTokens()) {
+        int value = Integer.parseInt(st.nextToken());
+        if ((value >= minValue) && (value <= maxValue)) {
+          valueList[value] = true;
+        } else {
+          throw new IllegalArgumentException();
+        }
+      }
+      return valueList;
+    }
+
+    throw new IllegalArgumentException();
+  }
+
+  /**
+   * Get next reccuring slot from the range.
+   * @param timesList the range.
+   * @param fromNow the current slot.
+   * @return next recurring slot in the range.
+   */
+  private int getNextTimeSlice(boolean[] timesList, int fromNow)
+  {
+    for (int i = fromNow; i < timesList.length; i++) {
+      if (timesList[i]) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Get next task iteration date according to recurring schedule.
+   * @return next task iteration date.
+   * @throws IllegalArgumentException if recurring schedule is invalid.
+   */
+  private Date getNextIteration() throws IllegalArgumentException
+  {
+    int minute, hour, day, month, weekday;
+    GregorianCalendar calendar = new GregorianCalendar();
+    calendar.setFirstDayOfWeek(GregorianCalendar.SUNDAY);
+    calendar.add(GregorianCalendar.MINUTE, 1);
+    calendar.set(GregorianCalendar.SECOND, 0);
+    calendar.setLenient(false);
+
+    // Weekday
+    for (;;) {
+      // Month
+      for (;;) {
+        // Day
+        for (;;) {
+          // Hour
+          for (;;) {
+            // Minute
+            for (;;) {
+              minute = getNextTimeSlice(minutesArray,
+                calendar.get(GregorianCalendar.MINUTE));
+              if (minute == -1) {
+                calendar.set(GregorianCalendar.MINUTE, 0);
+                calendar.add(GregorianCalendar.HOUR_OF_DAY, 1);
+              } else {
+                calendar.set(GregorianCalendar.MINUTE, minute);
+                break;
+              }
+            }
+            hour = getNextTimeSlice(hoursArray,
+              calendar.get(GregorianCalendar.HOUR_OF_DAY));
+            if (hour == -1) {
+              calendar.set(GregorianCalendar.HOUR_OF_DAY, 0);
+              calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
+            } else {
+              calendar.set(GregorianCalendar.HOUR_OF_DAY, hour);
+              break;
+            }
+          }
+          day = getNextTimeSlice(daysArray,
+            calendar.get(GregorianCalendar.DAY_OF_MONTH));
+          if (day == -1) {
+            calendar.set(GregorianCalendar.DAY_OF_MONTH, 1);
+            calendar.add(GregorianCalendar.MONTH, 1);
+          } else {
+            calendar.set(GregorianCalendar.DAY_OF_MONTH, day);
+            break;
+          }
+        }
+        month = getNextTimeSlice(monthArray,
+          (calendar.get(GregorianCalendar.MONTH) + 1));
+        if (month == -1) {
+          calendar.set(GregorianCalendar.MONTH, 0);
+          calendar.add(GregorianCalendar.YEAR, 1);
+        } else {
+          calendar.set(GregorianCalendar.MONTH, (month - 1));
+          break;
+        }
+      }
+      weekday = getNextTimeSlice(weekdayArray,
+        (calendar.get(GregorianCalendar.DAY_OF_WEEK) - 1));
+      if ((weekday == -1) ||
+          (weekday != (calendar.get(
+           GregorianCalendar.DAY_OF_WEEK) - 1)))
+      {
+        calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
+      } else {
+        break;
+      }
+    }
+
+    return calendar.getTime();
   }
 }
-

--
Gitblit v1.10.0