mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

abobrov
15.07.2008 2273c26793fe6e3abfd90a400823e8e46b3303bb
- [Issue 274]  Recurring Tasks
24 files modified
1405 ■■■■ changed files
opends/resource/schema/02-config.ldif 12 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/backend.properties 41 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/task.properties 1 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/tools.properties 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/RecurringTask.java 406 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/Task.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 140 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskScheduler.java 184 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskState.java 32 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskThread.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/BackupTask.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/BackUpDB.java 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/ExportLDIF.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/ImportLDIF.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/ManageTasks.java 40 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/RestoreDB.java 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/ToolConstants.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskClient.java 94 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskEntry.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java 17 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/tasks/TaskTool.java 135 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java 65 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java 131 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif
@@ -311,8 +311,8 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.60
  NAME 'ds-recurring-task-class-name'
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.515
  NAME 'ds-recurring-task-schedule'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
@@ -2728,14 +2728,12 @@
        ds-cfg-profile-sample-interval $
        ds-cfg-profile-action )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.38
objectClasses: ( 1.3.6.1.4.1.26027.1.2.198
  NAME 'ds-recurring-task'
  SUP top
  STRUCTURAL
  MUST ( ds-recurring-task-class-name $
  AUXILIARY
  MUST ( ds-recurring-task-schedule $
         ds-recurring-task-id )
  MAY ( ds-task-notify-on-completion $
        ds-task-notify-on-error )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.39
  NAME 'ds-cfg-root-config'
opends/src/messages/messages/backend.properties
@@ -318,18 +318,18 @@
SEVERE_ERR_RECURRINGTASK_MULTIPLE_ID_VALUES_103=The provided recurring task \
 entry contains multiple values for the %s attribute, which is used to specify \
 the recurring task ID, but only a single value is allowed
SEVERE_ERR_RECURRINGTASK_NO_CLASS_ATTRIBUTE_104=The provided recurring task \
 entry does not contain attribute %s which is needed to specify the \
 fully-qualified name of the class providing the task logic
SEVERE_ERR_RECURRINGTASK_MULTIPLE_CLASS_TYPES_105=The provided recurring task \
 entry contains multiple attributes with type %s, which is used to hold the \
 task class name, but only a single instance is allowed
SEVERE_ERR_RECURRINGTASK_NO_CLASS_VALUES_106=The provided recurring task \
SEVERE_ERR_RECURRINGTASK_NO_SCHEDULE_ATTRIBUTE_104=The provided recurring task \
 entry does not contain attribute %s which is needed to specify recurring task \
 schedule
SEVERE_ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_TYPES_105=The provided recurring \
 task entry contains multiple attributes with type %s, which is used to hold \
 recurring task schedule, but only a single instance is allowed
SEVERE_ERR_RECURRINGTASK_NO_SCHEDULE_VALUES_106=The provided recurring task \
 entry does not contain any values for the %s attribute, which is used to \
 specify the fully-qualified name of the class providing the task logic
SEVERE_ERR_RECURRINGTASK_MULTIPLE_CLASS_VALUES_107=The provided recurring \
 specify recurring task schedule
SEVERE_ERR_RECURRINGTASK_MULTIPLE_SCHEDULE_VALUES_107=The provided recurring \
 task entry contains multiple values for the %s attribute, which is used to \
 specify the task class name, but only a single value is allowed
 specify recurring task schedule, but only a single value is allowed
SEVERE_ERR_RECURRINGTASK_CANNOT_LOAD_CLASS_108=An error occurred while \
 attempting to load class %s specified in attribute %s of the provided \
 recurring task entry:  %s.  Does this class exist in the Directory Server \
@@ -1035,3 +1035,24 @@
MILD_ERR_NUM_SUBORDINATES_NOT_SUPPORTED_369=This backend does not provide \
 support for the numSubordinates operational attribute
NOTICE_BACKEND_OFFLINE_370=The backend %s is now taken offline
SEVERE_ERR_RECURRINGTASK_INVALID_N_TOKENS_371=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid number \
 of tokens
SEVERE_ERR_RECURRINGTASK_INVALID_MINUTE_TOKEN_372=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid minute \
 token
SEVERE_ERR_RECURRINGTASK_INVALID_HOUR_TOKEN_373=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid hour \
 token
SEVERE_ERR_RECURRINGTASK_INVALID_DAY_TOKEN_374=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid day of \
 the month token
SEVERE_ERR_RECURRINGTASK_INVALID_MONTH_TOKEN_375=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid month of \
 the year token
SEVERE_ERR_RECURRINGTASK_INVALID_WEEKDAY_TOKEN_376=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid day of the \
 week token
SEVERE_ERR_RECURRINGTASK_INVALID_TOKENS_COMBO_377=The provided recurring task \
 entry attribute %s holding the recurring task schedule has invalid tokens \
 combination yielding a nonexistent calendar date
opends/src/messages/messages/task.properties
@@ -193,3 +193,4 @@
INFO_IMPORT_ARG_RANDOM_SEED_105=Random Seed
SEVERE_ERR_TASK_LDAP_FAILED_TO_CONNECT_WRONG_PORT_106=Unable to connect to the \
 server at %s on port %s. Check this port is an administration port
INFO_TASK_STATE_RECURRING_107=Recurring
opends/src/messages/messages/tools.properties
@@ -2144,7 +2144,7 @@
  be canceled
INFO_TASKINFO_NO_CANCELABLE_TASKS_1452=There are currently no cancelable tasks
SEVERE_ERR_TASK_CLIENT_UNKNOWN_TASK_1453=There are no tasks defined with ID '%s'
SEVERE_ERR_TASK_CLIENT_UNCANCELABLE_TASK_1454=Task '%s' is has finished and \
SEVERE_ERR_TASK_CLIENT_UNCANCELABLE_TASK_1454=Task '%s' has finished and \
  cannot be canceled
SEVERE_ERR_TASK_CLIENT_TASK_STATE_UNKNOWN_1455=State for task '%s' cannot be \
  determined
@@ -2431,3 +2431,9 @@
cannot be backuped is the directory %s: this directory is already a backup \
directory for backend %s
INFO_RECURRING_TASK_PLACEHOLDER_1651={schedulePattern}
INFO_DESCRIPTION_RECURRING_TASK_1652=Indicates the task is recurring and will \
 be scheduled according to the value argument expressed in crontab(5) \
 compatible time/date pattern
INFO_TASK_TOOL_RECURRING_TASK_SCHEDULED_1653=Recurring %s task %s scheduled \
 successfully
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();
  }
}
opends/src/server/org/opends/server/backends/task/Task.java
@@ -580,6 +580,18 @@
  }
  /**
   * Indicates whether or not this task is an iteration of
   * some recurring task.
   *
   * @return boolean where true indicates that this task is
   *         recurring, false otherwise.
   */
  public boolean isRecurring()
  {
    return (recurringTaskID != null);
  }
  /**
   * Indicates whether or not this task has been cancelled.
   *
   * @return boolean where true indicates that this task was
opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -617,7 +617,11 @@
      TaskState state = t.getTaskState();
      if (TaskState.isPending(state))
      {
        taskScheduler.removePendingTask(t.getTaskID());
        if (t.isRecurring()) {
          taskScheduler.removeRecurringTaskIteration(t.getTaskID());
        } else {
          taskScheduler.removePendingTask(t.getTaskID());
        }
      }
      else if (TaskState.isDone(t.getTaskState()))
      {
@@ -641,9 +645,6 @@
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
      }
      // Try to remove the recurring task.  This will fail if there are any
      // associated iterations pending or running.
      taskScheduler.removeRecurringTask(rt.getRecurringTaskID());
    }
    else
@@ -707,13 +708,12 @@
          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
        }
        // Look at the state of the task.  We will allow anything to be altered
        // for a pending task.  For a running task, we will only allow the state
        // to be altered in order to cancel it.  We will not allow any
        // modifications for completed tasks.
        TaskState state = t.getTaskState();
        if (TaskState.isPending(state))
        if (TaskState.isPending(state) && !t.isRecurring())
        {
          Task newTask = taskScheduler.entryToScheduledTask(newEntry,
              modifyOperation);
@@ -727,50 +727,7 @@
          // This will only be allowed using the replace modification type on
          // the ds-task-state attribute if the value starts with "cancel" or
          // "stop".  In that case, we'll cancel the task.
          boolean acceptable = true;
          for (Modification m : modifyOperation.getModifications())
          {
            if (m.isInternal())
            {
              continue;
            }
            if (m.getModificationType() != ModificationType.REPLACE)
            {
              acceptable = false;
              break;
            }
            Attribute a = m.getAttribute();
            AttributeType at = a.getAttributeType();
            if (! at.hasName(ATTR_TASK_STATE))
            {
              acceptable = false;
              break;
            }
            Iterator<AttributeValue> iterator = a.iterator();
            if (! iterator.hasNext())
            {
              acceptable = false;
              break;
            }
            AttributeValue v = iterator.next();
            String valueString = toLowerCase(v.getStringValue());
            if (! (valueString.startsWith("cancel") ||
                   valueString.startsWith("stop")))
            {
              acceptable = false;
              break;
            }
            if (iterator.hasNext())
            {
              acceptable = false;
              break;
            }
          }
          boolean acceptable = isReplaceEntryAcceptable(modifyOperation);
          if (acceptable)
          {
@@ -786,6 +743,37 @@
                                         message);
          }
        }
        else if (TaskState.isPending(state) && t.isRecurring())
        {
          // Pending recurring task iterations can only be canceled.
          boolean acceptable = isReplaceEntryAcceptable(modifyOperation);
          if (acceptable)
          {
            Task newTask = taskScheduler.entryToScheduledTask(newEntry,
              modifyOperation);
            if (newTask.getTaskState() ==
              TaskState.CANCELED_BEFORE_STARTING)
            {
              taskScheduler.removePendingTask(t.getTaskID());
              taskScheduler.scheduleTask(newTask, true);
            }
            else if (newTask.getTaskState() ==
              TaskState.STOPPED_BY_ADMINISTRATOR)
            {
              Message message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get();
              t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message);
            }
              return;
          }
          else
          {
            Message message =
              ERR_TASKBE_MODIFY_RECURRING.get(String.valueOf(entryDN));
            throw new DirectoryException(
              ResultCode.UNWILLING_TO_PERFORM, message);
          }
        }
        else
        {
          Message message =
@@ -820,6 +808,58 @@
  /**
   * Helper to determine if requested modifications are acceptable.
   * @param modifyOperation associated with requested modifications.
   * @return <CODE>true</CODE> if requested modifications are
   *         acceptable, <CODE>false</CODE> otherwise.
   */
  private boolean isReplaceEntryAcceptable(ModifyOperation modifyOperation)
  {
    boolean acceptable = true;
    for (Modification m : modifyOperation.getModifications()) {
      if (m.isInternal()) {
        continue;
      }
      if (m.getModificationType() != ModificationType.REPLACE) {
        acceptable = false;
        break;
      }
      Attribute a = m.getAttribute();
      AttributeType at = a.getAttributeType();
      if (!at.hasName(ATTR_TASK_STATE)) {
        acceptable = false;
        break;
      }
      Iterator<AttributeValue> iterator = a.iterator();
      if (!iterator.hasNext()) {
        acceptable = false;
        break;
      }
      AttributeValue v = iterator.next();
      String valueString = toLowerCase(v.getStringValue());
      if (!(valueString.startsWith("cancel") ||
        valueString.startsWith("stop"))) {
        acceptable = false;
        break;
      }
      if (iterator.hasNext()) {
        acceptable = false;
        break;
      }
    }
    return acceptable;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
opends/src/server/org/opends/server/backends/task/TaskScheduler.java
@@ -37,6 +37,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -55,6 +56,7 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
@@ -191,6 +193,27 @@
    DirectoryServer.registerAlertGenerator(this);
    initializeTasksFromBackingFile();
    for (RecurringTask recurringTask : recurringTasks.values()) {
      Task task = null;
      try {
        task = recurringTask.scheduleNextIteration();
      } catch (DirectoryException de) {
        logError(de.getMessageObject());
      }
      if (task != null) {
        try {
          scheduleTask(task, false);
        } catch (DirectoryException de) {
          // This task might have been already scheduled from before
          // and thus got initialized from backing file, otherwise
          // log error and continue.
          if (de.getResultCode() != ResultCode.ENTRY_ALREADY_EXISTS) {
            logError(de.getMessageObject());
          }
        }
      }
    }
  }
@@ -224,7 +247,12 @@
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
      }
      recurringTasks.put(id, recurringTask);
      Attribute attr = Attributes.create(ATTR_TASK_STATE,
        TaskState.RECURRING.toString());
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(attr);
      Entry recurringTaskEntry = recurringTask.getRecurringTaskEntry();
      recurringTaskEntry.putAttribute(attr.getAttributeType(), attrList);
      if (scheduleIteration)
      {
@@ -236,6 +264,7 @@
        }
      }
      recurringTasks.put(id, recurringTask);
      writeState();
    }
    finally
@@ -264,24 +293,19 @@
    try
    {
      RecurringTask recurringTask = recurringTasks.remove(recurringTaskID);
      writeState();
      for (Task t : tasks.values())
      {
        if ((t.getRecurringTaskID() != null) &&
            (t.getRecurringTaskID().equals(recurringTaskID)) &&
            (! TaskState.isDone(t.getTaskState())))
            (!TaskState.isDone(t.getTaskState())))
        {
          Message message = ERR_TASKSCHED_REMOVE_RECURRING_EXISTING_ITERATION.
              get(String.valueOf(recurringTaskID),
                  String.valueOf(t.getTaskID()));
          throw new DirectoryException(
                  ResultCode.UNWILLING_TO_PERFORM, message);
          cancelTask(t.getTaskID());
        }
      }
      RecurringTask recurringTask = recurringTasks.remove(recurringTaskID);
      writeState();
      return recurringTask;
    }
    finally
@@ -348,7 +372,15 @@
      }
      else if (TaskState.isDone(state))
      {
        completedTasks.add(task);
        if ((state == TaskState.CANCELED_BEFORE_STARTING) &&
          task.isRecurring())
        {
          pendingTasks.add(task);
        }
        else
        {
          completedTasks.add(task);
        }
      }
      else
      {
@@ -459,6 +491,53 @@
  /**
   * Removes the specified pending iteration of recurring task. It will
   * be removed from the task set but still be kept in the pending set.
   *
   * @param  taskID  The task ID of the pending iteration to remove.
   *
   * @return  The task that was removed.
   *
   * @throws  DirectoryException  If the requested task is not in the
   *                              pending queue.
   */
  public Task removeRecurringTaskIteration(String taskID)
         throws DirectoryException
  {
    schedulerLock.lock();
    try
    {
      Task t = tasks.get(taskID);
      if (t == null)
      {
        Message message = ERR_TASKSCHED_REMOVE_PENDING_NO_SUCH_TASK.get(
            String.valueOf(taskID));
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
      }
      if (TaskState.isPending(t.getTaskState()))
      {
        tasks.remove(taskID);
        writeState();
        return t;
      }
      else
      {
        Message message = ERR_TASKSCHED_REMOVE_PENDING_NOT_PENDING.get(
            String.valueOf(taskID));
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
      }
    }
    finally
    {
      schedulerLock.unlock();
    }
  }
  /**
   * Removes the specified completed task.
   *
   * @param  taskID  The task ID of the completed task to remove.
@@ -546,7 +625,12 @@
        }
        else
        {
          Task newIteration = recurringTask.scheduleNextIteration();
          Task newIteration = null;
          try {
            newIteration = recurringTask.scheduleNextIteration();
          } catch (DirectoryException de) {
            logError(de.getMessageObject());
          }
          if (newIteration != null)
          {
            // FIXME -- What to do if new iteration is null?
@@ -746,6 +830,7 @@
   * Operates in a loop, launching tasks at the appropriate time and performing
   * any necessary periodic cleanup.
   */
  @Override
  public void run()
  {
    isRunning       = true;
@@ -797,6 +882,31 @@
              long waitTime = t.getScheduledStartTime() - TimeThread.getTime();
              sleepTime = Math.min(sleepTime, waitTime);
            }
            // Recurring task iteration has to spawn the next one
            // even if the current iteration has been canceled.
            else if ((state == TaskState.CANCELED_BEFORE_STARTING) &&
                     t.isRecurring())
            {
              if (t.getScheduledStartTime() > TimeThread.getTime()) {
                // If we're waiting for the start time to arrive,
                // then see if that will come before the next
                // sleep time is up.
                long waitTime =
                  t.getScheduledStartTime() - TimeThread.getTime();
                sleepTime = Math.min(sleepTime, waitTime);
              } else {
                TaskThread taskThread;
                if (idleThreads.isEmpty()) {
                  taskThread = new TaskThread(this, nextThreadID++);
                  taskThread.start();
                } else {
                  taskThread = idleThreads.removeFirst();
                }
                runningTasks.add(t);
                activeThreads.put(t.getTaskID(), taskThread);
                taskThread.setTask(t);
              }
            }
            if (state != t.getTaskState())
            {
@@ -876,7 +986,13 @@
  {
    // If the task has finished we don't want to restart it
    TaskState state = task.getTaskState();
    if (state != null && TaskState.isDone(state))
    // Reset task state if recurring.
    if (state == TaskState.RECURRING) {
      state = null;
    }
    if ((state != null) && TaskState.isDone(state))
    {
      return state;
    }
@@ -1011,26 +1127,6 @@
                    String.valueOf(taskBackend.getTaskRootDN()));
            logError(message);
          }
          else if (parentDN.equals(taskBackend.getRecurringTasksParentDN()))
          {
            try
            {
              RecurringTask recurringTask = entryToRecurringTask(entry);
              addRecurringTask(recurringTask, false);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message message =
                  ERR_TASKSCHED_CANNOT_SCHEDULE_RECURRING_TASK_FROM_ENTRY.
                    get(String.valueOf(entryDN), de.getMessageObject());
              logError(message);
            }
          }
          else if (parentDN.equals(taskBackend.getScheduledTasksParentDN()))
          {
            try
@@ -1057,6 +1153,26 @@
              logError(message);
            }
          }
          else if (parentDN.equals(taskBackend.getRecurringTasksParentDN()))
          {
            try
            {
              RecurringTask recurringTask = entryToRecurringTask(entry);
              addRecurringTask(recurringTask, false);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              Message message =
                  ERR_TASKSCHED_CANNOT_SCHEDULE_RECURRING_TASK_FROM_ENTRY.
                    get(String.valueOf(entryDN), de.getMessageObject());
              logError(message);
            }
          }
          else
          {
            Message message = ERR_TASKSCHED_INVALID_TASK_ENTRY_DN.get(
opends/src/server/org/opends/server/backends/task/TaskState.java
@@ -76,6 +76,13 @@
  /**
   * The task state that indicates that the task is recurring.
   */
  RECURRING(INFO_TASK_STATE_RECURRING.get()),
  /**
   * The task state that indicates that the task has completed without any
   * errors.
   */
@@ -173,6 +180,27 @@
  /**
   * Indicates whether a task with the specified state is recurring.
   *
   * @param  taskState  The task state for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the task state indicates that the task
   *          is recurring, or <CODE>false</CODE> otherwise.
   */
  public static boolean isRecurring(TaskState taskState)
  {
    switch (taskState)
    {
      case RECURRING:
        return true;
      default:
        return false;
    }
  }
  /**
   * Indicates whether a task with the specified state has completed all the
   * processing that it will do, regardless of whether it completed its
   * intended goal.
@@ -278,6 +306,10 @@
    {
      return RUNNING;
    }
    else if (lowerString.equals("recurring"))
    {
      return RECURRING;
    }
    else if (lowerString.equals("completed_successfully"))
    {
      return COMPLETED_SUCCESSFULLY;
opends/src/server/org/opends/server/backends/task/TaskThread.java
@@ -194,8 +194,11 @@
      try
      {
        TaskState returnState = getAssociatedTask().execute();
        getAssociatedTask().setTaskState(returnState);
        if (!TaskState.isDone(getAssociatedTask().getTaskState()))
        {
          TaskState returnState = getAssociatedTask().execute();
          getAssociatedTask().setTaskState(returnState);
        }
      }
      catch (Exception e)
      {
opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -2109,11 +2109,11 @@
  /**
   * The name of the configuration attribute that holds the name of the class
   * used to provide the implementation logic for a recurring task.
   * The name of the configuration attribute that holds the
   * schedule for a recurring task.
   */
  public static final String ATTR_RECURRING_TASK_CLASS_NAME =
       NAME_PREFIX_RECURRING_TASK + "class-name";
  public static final String ATTR_RECURRING_TASK_SCHEDULE =
       NAME_PREFIX_RECURRING_TASK + "schedule";
opends/src/server/org/opends/server/tasks/BackupTask.java
@@ -147,6 +147,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public Message getDisplayName() {
    return INFO_TASK_BACKUP_NAME.get();
  }
@@ -154,6 +155,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public Message getAttributeDisplayName(String attrName) {
    return argDisplayMap.get(attrName);
  }
@@ -284,6 +286,12 @@
    }
    // Use task id for backup id in case of recurring task.
    if (super.isRecurring()) {
      backupID = super.getTaskID();
    }
    // If no backup ID was provided, then create one with the current timestamp.
    if (backupID == null)
    {
@@ -575,6 +583,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void interruptTask(TaskState interruptState, Message interruptReason)
  {
    if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) &&
@@ -591,6 +600,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isInterruptable() {
    return true;
  }
opends/src/server/org/opends/server/tools/BackUpDB.java
@@ -1109,5 +1109,16 @@
    }
    return ret;
  }
  /**
   * {@inheritDoc}
   */
  public String getTaskId() {
    if (backupIDString != null) {
      return backupIDString.getValue();
    } else {
      return null;
    }
  }
}
opends/src/server/org/opends/server/tools/ExportLDIF.java
@@ -1029,5 +1029,13 @@
      return 1;
    }
  }
  /**
   * {@inheritDoc}
   */
  public String getTaskId() {
    // NYI.
    return null;
  }
}
opends/src/server/org/opends/server/tools/ImportLDIF.java
@@ -1414,5 +1414,13 @@
    importConfig.close();
    return retCode;
  }
  /**
   * {@inheritDoc}
   */
  public String getTaskId() {
    // NYI.
    return null;
  }
}
opends/src/server/org/opends/server/tools/ManageTasks.java
@@ -62,6 +62,7 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.opends.server.backends.task.TaskState;
/**
 * Tool for getting information and managing tasks in the Directory Server.
@@ -423,12 +424,11 @@
                new TaskDrilldownMenu(taskId),
                taskEntry.getType(), taskEntry.getState());
        index++;
        if (taskEntry.isCancelable() && !taskEntry.isDone()) {
        if (taskEntry.isCancelable()) {
          cancelableIndices.add(index);
        }
      }
    } else {
      // println();
      getOutputStream().println(INFO_TASKINFO_NO_TASKS.get());
      getOutputStream().println();
    }
@@ -621,6 +621,7 @@
    public MenuResult<TaskEntry> invoke(ManageTasks app)
            throws CLIException
    {
      Message m = null;
      TaskEntry taskEntry = null;
      try {
        taskEntry = app.getTaskClient().getTaskEntry(taskId);
@@ -640,23 +641,30 @@
        table.appendCell(INFO_TASKINFO_FIELD_STATUS.get());
        table.appendCell(taskEntry.getState());
        table.startRow();
        table.appendCell(INFO_TASKINFO_FIELD_SCHEDULED_START.get());
        Message m = taskEntry.getScheduledStartTime();
        if (m == null || m.equals(Message.EMPTY)) {
          table.appendCell(INFO_TASKINFO_IMMEDIATE_EXECUTION.get());
        } else {
        if (TaskState.isRecurring(taskEntry.getTaskState())) {
          table.startRow();
          table.appendCell(INFO_TASKINFO_FIELD_SCHEDULED_START.get());
          m = taskEntry.getScheduleTab();
          table.appendCell(m);
        } else {
          table.startRow();
          table.appendCell(INFO_TASKINFO_FIELD_SCHEDULED_START.get());
          m = taskEntry.getScheduledStartTime();
          if (m == null || m.equals(Message.EMPTY)) {
            table.appendCell(INFO_TASKINFO_IMMEDIATE_EXECUTION.get());
          } else {
            table.appendCell(m);
          }
          table.startRow();
          table.appendCell(INFO_TASKINFO_FIELD_ACTUAL_START.get());
          table.appendCell(taskEntry.getActualStartTime());
          table.startRow();
          table.appendCell(INFO_TASKINFO_FIELD_COMPLETION_TIME.get());
          table.appendCell(taskEntry.getCompletionTime());
        }
        table.startRow();
        table.appendCell(INFO_TASKINFO_FIELD_ACTUAL_START.get());
        table.appendCell(taskEntry.getActualStartTime());
        table.startRow();
        table.appendCell(INFO_TASKINFO_FIELD_COMPLETION_TIME.get());
        table.appendCell(taskEntry.getCompletionTime());
        writeMultiValueCells(
                table,
                INFO_TASKINFO_FIELD_DEPENDENCY.get(),
opends/src/server/org/opends/server/tools/RestoreDB.java
@@ -701,5 +701,16 @@
    }
    return 0;
  }
  /**
   * {@inheritDoc}
   */
  public String getTaskId() {
    if (backupIDString != null) {
      return backupIDString.getValue();
    } else {
      return null;
    }
  }
}
opends/src/server/org/opends/server/tools/ToolConstants.java
@@ -576,6 +576,16 @@
  public static final Character OPTION_SHORT_START_DATETIME = 't';
  /**
   * Recurring task option long form.
   */
  public static final String OPTION_LONG_RECURRING_TASK = "recurringTask";
  /**
   * Recurring task option short form.
   */
  public static final Character OPTION_SHORT_RECURRING_TASK = null;
  /**
   * The value for the long option propertiesFilePAth .
   */
  public static final String OPTION_LONG_PROP_FILE_PATH = "propertiesFilePath";
opends/src/server/org/opends/server/tools/tasks/TaskClient.java
@@ -69,7 +69,10 @@
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
import org.opends.server.protocols.ldap.DeleteResponseProtocolOp;
/**
 * Helper class for interacting with the task backend on behalf of utilities
@@ -109,16 +112,33 @@
  public synchronized TaskEntry schedule(TaskScheduleInformation information)
          throws LDAPException, IOException, ASN1Exception, TaskClientException
  {
    String taskID = null;
    ASN1OctetString entryDN = null;
    boolean scheduleRecurring = false;
    LDAPReader reader = connection.getLDAPReader();
    LDAPWriter writer = connection.getLDAPWriter();
    // Use a formatted time/date for the ID so that is remotely useful
    SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssMM");
    String taskID = df.format(new Date());
    if (information.getRecurringDateTime() != null) {
      scheduleRecurring = true;
    }
    ASN1OctetString entryDN =
         new ASN1OctetString(ATTR_TASK_ID + "=" + taskID + "," +
                             SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT);
    if (scheduleRecurring) {
      taskID = information.getTaskId();
      if ((taskID == null) || taskID.length() == 0) {
        taskID = information.getTaskClass().getSimpleName() +
          "-" + UUID.randomUUID().toString();
      }
      entryDN = new ASN1OctetString(ATTR_RECURRING_TASK_ID + "=" +
        taskID + "," + RECURRING_TASK_BASE_RDN + "," + DN_TASK_ROOT);
    } else {
      // Use a formatted time/date for the ID so that is remotely useful
      SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssMM");
      taskID = df.format(new Date());
      entryDN = new ASN1OctetString(ATTR_TASK_ID + "=" + taskID + "," +
        SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT);
    }
    ArrayList<LDAPControl> controls = new ArrayList<LDAPControl>();
@@ -127,11 +147,20 @@
    ArrayList<ASN1OctetString> ocValues = new ArrayList<ASN1OctetString>(3);
    ocValues.add(new ASN1OctetString("top"));
    ocValues.add(new ASN1OctetString(ConfigConstants.OC_TASK));
    if (scheduleRecurring) {
      ocValues.add(new ASN1OctetString(ConfigConstants.OC_RECURRING_TASK));
    }
    ocValues.add(new ASN1OctetString(information.getTaskObjectclass()));
    attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, ocValues));
    ArrayList<ASN1OctetString> taskIDValues = new ArrayList<ASN1OctetString>(1);
    taskIDValues.add(new ASN1OctetString(taskID));
    if (scheduleRecurring) {
      attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_ID, taskIDValues));
    }
    attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskIDValues));
    ArrayList<ASN1OctetString> classValues = new ArrayList<ASN1OctetString>(1);
@@ -149,6 +178,15 @@
              startDateValues));
    }
    if (scheduleRecurring) {
      ArrayList<ASN1OctetString> recurringPatternValues =
        new ArrayList<ASN1OctetString>(1);
      recurringPatternValues.add(new ASN1OctetString(
        information.getRecurringDateTime()));
      attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_SCHEDULE,
        recurringPatternValues));
    }
    // add dependency IDs
    List<String> dependencyIds = information.getDependencyIds();
    if (dependencyIds != null && dependencyIds.size() > 0) {
@@ -340,14 +378,13 @@
   * Changes that the state of the task in the backend to a canceled state.
   *
   * @param  id if the task to cancel
   * @return Entry of the task before the modification
   * @throws IOException if there is a stream communication problem
   * @throws LDAPException if there is a problem getting information
   *         out to the directory
   * @throws ASN1Exception if there is a problem with the encoding
   * @throws TaskClientException if there is no task with the requested id
   */
  public synchronized TaskEntry cancelTask(String id)
  public synchronized void cancelTask(String id)
          throws TaskClientException, IOException, ASN1Exception, LDAPException
  {
    LDAPReader reader = connection.getLDAPReader();
@@ -373,11 +410,6 @@
        LDAPAttribute attr = new LDAPAttribute(ATTR_TASK_STATE, values);
        mods.add(new LDAPModification(ModificationType.REPLACE, attr));
        // We have to reset the start time or the scheduler will
        // reschedule to task.
        // attr = new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME);
        // mods.add(new LDAPModification(ModificationType.DELETE, attr));
        ModifyRequestProtocolOp modRequest =
                new ModifyRequestProtocolOp(dn, mods);
        LDAPMessage requestMessage =
@@ -409,6 +441,41 @@
                  LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
                  errorMessage);
        }
      } else if (TaskState.isRecurring(state)) {
        ASN1OctetString dn = new ASN1OctetString(entry.getDN().toString());
        DeleteRequestProtocolOp deleteRequest =
          new DeleteRequestProtocolOp(dn);
        LDAPMessage requestMessage = new LDAPMessage(
          nextMessageID.getAndIncrement(), deleteRequest, null);
        writer.writeMessage(requestMessage);
        LDAPMessage responseMessage = reader.readMessage();
        if (responseMessage == null) {
          Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get();
          throw new LDAPException(UNAVAILABLE.getIntValue(), message);
        }
        if (responseMessage.getProtocolOpType() !=
                LDAPConstants.OP_TYPE_DELETE_RESPONSE)
        {
          throw new LDAPException(
                  LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
                  ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get(
                    responseMessage.getProtocolOpName()));
        }
        DeleteResponseProtocolOp deleteResponse =
                responseMessage.getDeleteResponseProtocolOp();
        Message errorMessage = deleteResponse.getErrorMessage();
        if (errorMessage != null) {
          throw new LDAPException(
                  LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR,
                  errorMessage);
        }
      } else {
        throw new TaskClientException(
                ERR_TASK_CLIENT_UNCANCELABLE_TASK.get(id));
@@ -417,7 +484,6 @@
      throw new TaskClientException(
              ERR_TASK_CLIENT_TASK_STATE_UNKNOWN.get(id));
    }
    return getTaskEntry(id);
  }
opends/src/server/org/opends/server/tools/tasks/TaskEntry.java
@@ -82,6 +82,8 @@
    supAttrNames.add("ds-task-log-message");
    supAttrNames.add("ds-task-notify-on-completion");
    supAttrNames.add("ds-task-notify-on-error");
    supAttrNames.add("ds-recurring-task-id");
    supAttrNames.add("ds-recurring-task-schedule");
  }
  private String id;
@@ -90,6 +92,7 @@
  private String schedStart;
  private String actStart;
  private String compTime;
  private String schedTab;
  private List<String> depends;
  private String depFailAct;
  private List<String> logs;
@@ -126,6 +129,7 @@
    logs =       getMultiStringValue(entry,  p + "log-message");
    notifyErr =  getMultiStringValue(entry,  p + "notify-on-error");
    notifyComp = getMultiStringValue(entry,  p + "notify-on-completion");
    schedTab =   getSingleStringValue(entry, "ds-recurring-task-schedule");
    // Build a map of non-superior attribute value pairs for display
@@ -223,6 +227,15 @@
  }
  /**
   * Gets recurring schedule tab.
   *
   * @return Message tab string
   */
  public Message getScheduleTab() {
    return Message.raw(schedTab);
  }
  /**
   * Gets the IDs of tasks upon which this task depends.
   *
   * @return array of IDs
@@ -326,6 +339,7 @@
    if (state != null) {
      Task task = getTask();
      cancelable = (TaskState.isPending(state) ||
        TaskState.isRecurring(state) ||
              (TaskState.isRunning(state) &&
                      task != null &&
                      task.isInterruptable()));
opends/src/server/org/opends/server/tools/tasks/TaskScheduleInformation.java
@@ -77,6 +77,23 @@
  /**
   * Gets an arbitrary task id assigned to this task.
   *
   * @return assigned task id if any or <CODE>null</CODE> otherwise.
   */
  String getTaskId();
  /**
   * Gets the date/time pattern for recurring task schedule.
   *
   * @return recurring date/time pattern at which the task
   *         should be scheduled.
   */
  String getRecurringDateTime();
  /**
   * Gets a list of task IDs upon which this task is dependent.
   *
   * @return list of task IDs
opends/src/server/org/opends/server/tools/tasks/TaskTool.java
@@ -88,6 +88,9 @@
  // Argument for describing the task's start time
  StringArgument startArg;
  // Argument to indicate a recurring task
  StringArgument recurringArg;
  // Argument for specifying completion notifications
  StringArgument completionNotificationArg;
@@ -133,79 +136,88 @@
   * @return LDAPConnectionArgumentParser for processing CLI input
   */
  protected LDAPConnectionArgumentParser createArgParser(String className,
      Message toolDescription)
    {
    Message toolDescription)
  {
    ArgumentGroup ldapGroup = new ArgumentGroup(
            INFO_DESCRIPTION_TASK_LDAP_ARGS.get(), 1001);
      INFO_DESCRIPTION_TASK_LDAP_ARGS.get(), 1001);
    argParser = new LDAPConnectionArgumentParser(className,
            toolDescription, false, ldapGroup, alwaysSSL);
      toolDescription, false, ldapGroup, alwaysSSL);
    ArgumentGroup taskGroup = new ArgumentGroup(
            INFO_DESCRIPTION_TASK_TASK_ARGS.get(), 1000);
      INFO_DESCRIPTION_TASK_TASK_ARGS.get(), 1000);
    try {
      StringArgument propertiesFileArgument = new StringArgument(
          "propertiesFilePath",
          null, OPTION_LONG_PROP_FILE_PATH,
          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
          INFO_DESCRIPTION_PROP_FILE_PATH.get());
        "propertiesFilePath",
        null, OPTION_LONG_PROP_FILE_PATH,
        false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
        INFO_DESCRIPTION_PROP_FILE_PATH.get());
      argParser.addArgument(propertiesFileArgument);
      argParser.setFilePropertiesArgument(propertiesFileArgument);
     BooleanArgument noPropertiesFileArgument = new BooleanArgument(
          "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
          INFO_DESCRIPTION_NO_PROP_FILE.get());
     argParser.addArgument(noPropertiesFileArgument);
     argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
      BooleanArgument noPropertiesFileArgument = new BooleanArgument(
        "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
        INFO_DESCRIPTION_NO_PROP_FILE.get());
      argParser.addArgument(noPropertiesFileArgument);
      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
      startArg = new StringArgument(
              OPTION_LONG_START_DATETIME,
              OPTION_SHORT_START_DATETIME,
              OPTION_LONG_START_DATETIME, false, false,
              true, INFO_START_DATETIME_PLACEHOLDER.get(),
              null, null,
              INFO_DESCRIPTION_START_DATETIME.get());
        OPTION_LONG_START_DATETIME,
        OPTION_SHORT_START_DATETIME,
        OPTION_LONG_START_DATETIME, false, false,
        true, INFO_START_DATETIME_PLACEHOLDER.get(),
        null, null,
        INFO_DESCRIPTION_START_DATETIME.get());
      argParser.addArgument(startArg, taskGroup);
      recurringArg = new StringArgument(
        OPTION_LONG_RECURRING_TASK,
        OPTION_SHORT_RECURRING_TASK,
        OPTION_LONG_RECURRING_TASK, false, false,
        true, INFO_RECURRING_TASK_PLACEHOLDER.get(),
        null, null,
        INFO_DESCRIPTION_RECURRING_TASK.get());
      argParser.addArgument(recurringArg, taskGroup);
      completionNotificationArg = new StringArgument(
              OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
              OPTION_SHORT_COMPLETION_NOTIFICATION_EMAIL,
              OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
              false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
              null, null, INFO_DESCRIPTION_TASK_COMPLETION_NOTIFICATION.get());
        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
        OPTION_SHORT_COMPLETION_NOTIFICATION_EMAIL,
        OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
        false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
        null, null, INFO_DESCRIPTION_TASK_COMPLETION_NOTIFICATION.get());
      argParser.addArgument(completionNotificationArg, taskGroup);
      errorNotificationArg = new StringArgument(
              OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
              OPTION_SHORT_ERROR_NOTIFICATION_EMAIL,
              OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
              false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
              null, null, INFO_DESCRIPTION_TASK_ERROR_NOTIFICATION.get());
        OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
        OPTION_SHORT_ERROR_NOTIFICATION_EMAIL,
        OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
        false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
        null, null, INFO_DESCRIPTION_TASK_ERROR_NOTIFICATION.get());
      argParser.addArgument(errorNotificationArg, taskGroup);
      dependencyArg = new StringArgument(
              OPTION_LONG_DEPENDENCY,
              OPTION_SHORT_DEPENDENCY,
              OPTION_LONG_DEPENDENCY,
              false, true, true, INFO_TASK_ID_PLACEHOLDER.get(),
              null, null, INFO_DESCRIPTION_TASK_DEPENDENCY_ID.get());
        OPTION_LONG_DEPENDENCY,
        OPTION_SHORT_DEPENDENCY,
        OPTION_LONG_DEPENDENCY,
        false, true, true, INFO_TASK_ID_PLACEHOLDER.get(),
        null, null, INFO_DESCRIPTION_TASK_DEPENDENCY_ID.get());
      argParser.addArgument(dependencyArg, taskGroup);
      Set fdaValSet = EnumSet.allOf(FailedDependencyAction.class);
      failedDependencyActionArg = new StringArgument(
              OPTION_LONG_FAILED_DEPENDENCY_ACTION,
              OPTION_SHORT_FAILED_DEPENDENCY_ACTION,
              OPTION_LONG_FAILED_DEPENDENCY_ACTION,
              false, true, true, INFO_ACTION_PLACEHOLDER.get(),
              null, null, INFO_DESCRIPTION_TASK_FAILED_DEPENDENCY_ACTION.get(
                StaticUtils.collectionToString(fdaValSet, ","),
                FailedDependencyAction.defaultValue().name()));
        OPTION_LONG_FAILED_DEPENDENCY_ACTION,
        OPTION_SHORT_FAILED_DEPENDENCY_ACTION,
        OPTION_LONG_FAILED_DEPENDENCY_ACTION,
        false, true, true, INFO_ACTION_PLACEHOLDER.get(),
        null, null, INFO_DESCRIPTION_TASK_FAILED_DEPENDENCY_ACTION.get(
        StaticUtils.collectionToString(fdaValSet, ","),
        FailedDependencyAction.defaultValue().name()));
      argParser.addArgument(failedDependencyActionArg, taskGroup);
      testIfOfflineArg = new BooleanArgument(
          "testIfOffline", null, "testIfOffline",
          INFO_DESCRIPTION_TEST_IF_OFFLINE.get());
        "testIfOffline", null, "testIfOffline",
        INFO_DESCRIPTION_TEST_IF_OFFLINE.get());
      testIfOfflineArg.setHidden(true);
      argParser.addArgument(testIfOfflineArg);
@@ -311,6 +323,19 @@
  /**
   * {@inheritDoc}
   */
  public String getRecurringDateTime() {
    String pattern = null;
    // If the recurring task arg is present parse its value
    if (recurringArg != null && recurringArg.isPresent()) {
      pattern = recurringArg.getValue();
    }
    return pattern;
  }
  /**
   * {@inheritDoc}
   */
  public List<String> getDependencyIds() {
    if (dependencyArg.isPresent()) {
      return dependencyArg.getValues();
@@ -405,13 +430,18 @@
        TaskClient tc = new TaskClient(conn);
        TaskEntry taskEntry = tc.schedule(this);
        Message startTime = taskEntry.getScheduledStartTime();
        if (startTime == null || startTime.length() == 0) {
        if (taskEntry.getTaskState() == TaskState.RECURRING) {
          out.println(
                  wrapText(INFO_TASK_TOOL_RECURRING_TASK_SCHEDULED.get(
                          taskEntry.getType(),
                          taskEntry.getId()),
                  MAX_LINE_WIDTH));
        } else if (startTime == null || startTime.length() == 0) {
          out.println(
                  wrapText(INFO_TASK_TOOL_TASK_SCHEDULED_NOW.get(
                          taskEntry.getType(),
                          taskEntry.getId()),
                  MAX_LINE_WIDTH));
        } else {
          out.println(
                  wrapText(INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE.get(
@@ -443,12 +473,13 @@
          } while (!taskEntry.isDone());
          if (TaskState.isSuccessful(taskEntry.getTaskState())) {
            out.println(
                wrapText(INFO_TASK_TOOL_TASK_SUCESSFULL.get(
                        taskEntry.getType(),
                        taskEntry.getId()),
                MAX_LINE_WIDTH));
            if (taskEntry.getTaskState() != TaskState.RECURRING) {
              out.println(
                  wrapText(INFO_TASK_TOOL_TASK_SUCESSFULL.get(
                          taskEntry.getType(),
                          taskEntry.getId()),
                  MAX_LINE_WIDTH));
            }
            return 0;
          } else {
            out.println(
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -168,7 +168,7 @@
   * cases that depend on this specific value of "o=test".
   */
  public static final String TEST_ROOT_DN_STRING = "o=test";
  /**
   * The backend if for the test backend
   */
@@ -178,7 +178,7 @@
   * The string representation of the OpenDMK jar file location
   * that will be used as base to determine if snmp is included or not
   */
  public static final String PROPERTY_OPENDMK_LOCATION =
  public static final String PROPERTY_OPENDMK_LOCATION =
          "org.opends.server.snmp.opendmk";
  /**
@@ -304,7 +304,7 @@
        testInstallRoot.mkdirs();
        testInstanceRoot.mkdirs();
      }
      File testInstanceSchema =
        new File (testInstanceRoot, "config" + File.separator + "schema");
      testInstanceSchema.mkdirs();
@@ -341,34 +341,34 @@
      File testClassesDir   = new File(testInstanceRoot, "classes");
      File testLibDir       = new File(testInstallRoot, "lib");
      File testBinDir       = new File(testInstallRoot, "bin");
      // Snmp resource
      String opendmkJarFileLocation =
      String opendmkJarFileLocation =
              System.getProperty(PROPERTY_OPENDMK_LOCATION);
      File opendmkJar = new File(opendmkJarFileLocation, "jdmkrt.jar");
      File   snmpResourceDir = new File(buildRoot + File.separator + "src" +
                                    File.separator + "snmp" + File.separator +
                                    "resource");
      File snmpConfigDir = new File(snmpResourceDir, "config");
      File testSnmpResourceDir = new File (testConfigDir + File.separator +
                                    "snmp");
      if (Boolean.getBoolean(PROPERTY_COPY_CLASSES_TO_TEST_PKG))
      {
        copyDirectory(serverClassesDir, testClassesDir);
        copyDirectory(unitClassesDir, testClassesDir);
      }
      if (installedRoot != null)
      {
        copyDirectory(new File(installedRoot), testInstallRoot);
        // Get the instance location
      }
      else
      {
@@ -508,7 +508,7 @@
      DirectoryEnvironmentConfig config = new DirectoryEnvironmentConfig();
      config.setServerRoot(testInstallRoot);
      config.setInstanceRoot(testInstanceRoot);
      config.setForceDaemonThreads(true);
      config.setConfigClass(ConfigFileHandler.class);
      config.setConfigFile(new File(testConfigDir, "config.ldif"));
@@ -648,7 +648,7 @@
    if (testConfigDir == null) {
      throw new RuntimeException("The testConfigDir variable is not set yet!");
    }
    return (testConfigDir);
  }
@@ -974,7 +974,7 @@
    in.close();
    out.close();
  }
  public static void appendFile(File src, File dst) throws IOException {
    InputStream in = new FileInputStream(src);
    OutputStream out = new FileOutputStream(dst, true);
@@ -988,7 +988,7 @@
    in.close();
    out.close();
  }
  /**
   * Get the LDAP port the test environment Directory Server instance is
@@ -1305,6 +1305,33 @@
  /**
   * Adds the provided entry to the Directory Server using an internal
   * operation.
   *
   * @param  lines  The lines that make up the entry to be added.
   *
   * @return result code for this operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  public static ResultCode addEntryOperation(String... lines)
         throws Exception
  {
    Entry entry = makeEntry(lines);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation = conn.processAdd(entry.getDN(),
                                     entry.getObjectClasses(),
                                     entry.getUserAttributes(),
                                     entry.getOperationalAttributes());
    return addOperation.getResultCode();
  }
  /**
   * Adds the provided set of entries to the Directory Server using internal
   * operations.
   *
@@ -1387,7 +1414,7 @@
      "-a",
      "-f", path
    };
    if (useAdminPort) {
      return LDAPModify.mainModify(adminArgs, false, null, null);
    } else {
@@ -1749,7 +1776,7 @@
    } catch (Exception e) {
       hostName="Unknown (" + e + ")";
    }
    String[] fullArgs = new String[args.length + 11];
    fullArgs[0] = "-h";
    fullArgs[1] = hostName;
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java
@@ -30,8 +30,10 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.UUID;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@@ -42,10 +44,10 @@
import org.opends.server.tasks.TasksTestCase;
import org.opends.server.types.DN;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -405,5 +407,130 @@
    assertEquals(resultCode, 0);
    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
  }
}
  /**
   * Tests basic recurring task functionality and parser.
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRecurringTask()
         throws Exception
  {
    String taskID = "testRecurringTask";
    String taskDN = "ds-recurring-task-id=" +
      taskID + ",cn=Recurring Tasks,cn=tasks";
    String taskSchedule = "00 * * * *";
    String[] invalidTaskSchedules = {
      "* * * *", "* * * * * *", "*:*:*:*:*",
      "60 * * * *", "-1 * * * *", "1-60 * * * *", "1,60 * * * *",
      "* 24 * * *", "* -1 * * *", "* 1-24 * * *", "* 1,24 * * *",
      "* * 32 * *", "* * 0 * *", "* * 1-32 * *", "* * 1,32 * *",
      "* * * 13 *", "* * * 0 *", "* * * 1-13 *", "* * * 1,13 *",
      "* * * * 7", "* * * * -1", "* * * * 1-7", "* * * * 1,7",
      "* * 31 2 *" };
    String[] validTaskSchedules = {
      "* * * * *",
      "59 * * * *", "0 * * * *", "0-59 * * * *", "0,59 * * * *",
      "* 23 * * *", "* 0 * * *", "* 0-23 * * *", "* 0,23 * * *",
      "* * 31 * *", "* * 1 * *", "* * 1-31 * *", "* * 1,31 * *",
      "* * * 12 *", "* * * 1 *", "* * * 1-12 *", "* * * 1,12 *",
      "* * * * 6", "* * * * 0", "* * * * 0-6", "* * * * 0,6" };
    GregorianCalendar calendar = new GregorianCalendar();
    calendar.setFirstDayOfWeek(GregorianCalendar.SUNDAY);
    calendar.setLenient(false);
    calendar.add(GregorianCalendar.HOUR_OF_DAY, 1);
    calendar.set(GregorianCalendar.MINUTE, 0);
    calendar.set(GregorianCalendar.SECOND, 0);
    Date scheduledDate = calendar.getTime();
    String scheduledTaskID = taskID + " - " + scheduledDate.toString();
    String scheduledTaskDN = "ds-task-id=" + scheduledTaskID +
      ",cn=Scheduled Tasks,cn=tasks";
    assertTrue(addRecurringTask(taskID, taskSchedule));
    Task scheduledTask = TasksTestCase.getTask(DN.decode(scheduledTaskDN));
    assertTrue(TaskState.isPending(scheduledTask.getTaskState()));
    // Perform a modification to update a non-state attribute.
    int resultCode = TestCaseUtils.applyModifications(true,
      "dn: " + taskDN,
      "changetype: modify",
      "replace: ds-recurring-task-schedule",
      "ds-recurring-task-schedule: * * * * *");
    assertFalse(resultCode == 0);
    // Delete recurring task.
    resultCode = TestCaseUtils.applyModifications(true,
      "dn: " + taskDN,
      "changetype: delete");
    assertEquals(resultCode, 0);
    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
    // Make sure scheduled task got canceled.
    scheduledTask = TasksTestCase.getTask(DN.decode(scheduledTaskDN));
    assertTrue(TaskState.isCancelled(scheduledTask.getTaskState()));
    // Test parser with invalid schedules.
    for (String invalidSchedule : invalidTaskSchedules) {
      assertFalse(addRecurringTask(taskID, invalidSchedule));
    }
    // Test parser with valid schedules.
    for (String validSchedule : validTaskSchedules) {
      taskID = "testRecurringTask" + "-" + UUID.randomUUID();
      taskDN = "ds-recurring-task-id=" + taskID +
        ",cn=Recurring Tasks,cn=tasks";
      assertTrue(addRecurringTask(taskID, validSchedule));
      // Delete recurring task.
      resultCode = TestCaseUtils.applyModifications(true,
        "dn: " + taskDN,
        "changetype: delete");
      assertEquals(resultCode, 0);
      assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
    }
  }
  /**
   * Adds recurring task to the task backend.
   *
   * @param  taskID  recurring task id.
   *
   * @param  taskSchedule  recurring task schedule.
   *
   * @throws  Exception  If an unexpected problem occurs.
   *
   * @return <CODE>true</CODE> if task successfully added to
   *         the task backend, <CODE>false</CODE> otherwise.
   */
  @Test(enabled=false) // This isn't a test method, but TestNG thinks it is.
  private boolean addRecurringTask(String taskID, String taskSchedule)
          throws Exception
  {
    String taskDN = "ds-recurring-task-id=" +
      taskID + ",cn=Recurring Tasks,cn=tasks";
    ResultCode rc = TestCaseUtils.addEntryOperation(
      "dn: " + taskDN,
      "objectClass: top",
      "objectClass: ds-task",
      "objectClass: ds-recurring-task",
      "objectClass: extensibleObject",
      "ds-recurring-task-id: " + taskID,
      "ds-recurring-task-schedule: " + taskSchedule,
      "ds-task-id: " + taskID,
      "ds-task-class-name: org.opends.server.tasks.DummyTask",
      "ds-task-dummy-sleep-time: 0");
    if (rc != ResultCode.SUCCESS) {
      return false;
    }
    return DirectoryServer.entryExists(DN.decode(taskDN));
  }
}