/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2013-2015 ForgeRock AS
 */
package org.opends.server.backends.task;

import static org.opends.messages.BackendMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.DirectoryThread;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.ServerContext;
import org.opends.server.types.*;
import org.opends.server.types.LockManager.DNLock;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.TimeThread;

/**
 * This class defines a task scheduler for the Directory Server that will
 * control the execution of scheduled tasks and other administrative functions
 * that need to occur on a regular basis.
 */
public class TaskScheduler
       extends DirectoryThread
       implements AlertGenerator
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /**
   * The fully-qualified name of this class.
   */
  private static final String CLASS_NAME =
       "org.opends.server.backends.task.TaskScheduler";



  /**
   * The maximum length of time in milliseconds to sleep between iterations
   * through the scheduler loop.
   */
  private static long MAX_SLEEP_TIME = 5000;


  /** Indicates whether the scheduler is currently running. */
  private boolean isRunning;
  /** Indicates whether a request has been received to stop the scheduler. */
  private boolean stopRequested;

  /** The entry that serves as the immediate parent for recurring tasks. */
  private Entry recurringTaskParentEntry;
  /** The entry that serves as the immediate parent for scheduled tasks. */
  private Entry scheduledTaskParentEntry;
  /** The top-level entry at the root of the task tree. */
  private Entry taskRootEntry;

  /** The set of recurring tasks defined in the server. */
  private final HashMap<String,RecurringTask> recurringTasks = new HashMap<>();
  /** The set of tasks associated with this scheduler. */
  private final HashMap<String,Task> tasks = new HashMap<>();
  /** The set of worker threads that are actively busy processing tasks. */
  private final HashMap<String,TaskThread> activeThreads = new HashMap<>();

  /** The thread ID for the next task thread to be created;. */
  private int nextThreadID = 1;

  /** The set of worker threads that may be used to process tasks. */
  private final LinkedList<TaskThread> idleThreads = new LinkedList<>();

  /** The lock used to provide threadsafe access to the scheduler. */
  private final ReentrantLock schedulerLock = new ReentrantLock();

  /** The task backend with which this scheduler is associated. */
  private TaskBackend taskBackend;

  /** The thread being used to actually run the scheduler. */
  private Thread schedulerThread;

  /** The set of recently-completed tasks that need to be retained. */
  private final TreeSet<Task> completedTasks = new TreeSet<>();
  /** The set of tasks that have been scheduled but not yet arrived. */
  private final TreeSet<Task> pendingTasks = new TreeSet<>();
  /** The set of tasks that are currently running. */
  private final TreeSet<Task> runningTasks = new TreeSet<>();

  private ServerContext serverContext;

  /**
   * Creates a new task scheduler that will be used to ensure that tasks are
   * invoked at the appropriate times.
   * @param serverContext
   *            The server context
   * @param  taskBackend  The task backend with which this scheduler is
   *                      associated.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the scheduler from the backing file.
   */
  public TaskScheduler(ServerContext serverContext, TaskBackend taskBackend)
         throws InitializationException
  {
    super("Task Scheduler Thread");

    this.serverContext = serverContext;
    this.taskBackend = taskBackend;

    DirectoryServer.registerAlertGenerator(this);

    initializeTasksFromBackingFile();

    for (RecurringTask recurringTask : recurringTasks.values()) {
      Task task = null;
      try {
        task = recurringTask.scheduleNextIteration(new GregorianCalendar());
      } catch (DirectoryException de) {
        logger.error(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) {
            logger.error(de.getMessageObject());
          }
        }
      }
    }
  }



  /**
   * Adds a recurring task to the scheduler, optionally scheduling the first
   * iteration for processing.
   *
   * @param  recurringTask      The recurring task to add to the scheduler.
   * @param  scheduleIteration  Indicates whether to schedule an iteration of
   *                            the recurring task.
   *
   * @throws  DirectoryException  If a problem occurs while trying to add the
   *                              recurring task (e.g., there's already another
   *                              recurring task defined with the same ID).
   */
  public void addRecurringTask(RecurringTask recurringTask,
                               boolean scheduleIteration)
         throws DirectoryException
  {
    schedulerLock.lock();

    try
    {
      String id = recurringTask.getRecurringTaskID();

      if (recurringTasks.containsKey(id))
      {
        LocalizableMessage message =
            ERR_TASKSCHED_DUPLICATE_RECURRING_ID.get(id);
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
      }

      Attribute attr = Attributes.create(ATTR_TASK_STATE, TaskState.RECURRING.toString());
      Entry recurringTaskEntry = recurringTask.getRecurringTaskEntry();
      recurringTaskEntry.putAttribute(attr.getAttributeType(), newArrayList(attr));

      if (scheduleIteration)
      {
        Task task = recurringTask.scheduleNextIteration(
                new GregorianCalendar());
        if (task != null)
        {
          // If there is an existing task with the same id
          // and it is in completed state, take its place.
          Task t = tasks.get(task.getTaskID());
          if (t != null && TaskState.isDone(t.getTaskState()))
          {
            removeCompletedTask(t.getTaskID());
          }

          scheduleTask(task, false);
        }
      }

      recurringTasks.put(id, recurringTask);
      writeState();
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Removes the recurring task with the given ID.
   *
   * @param  recurringTaskID  The ID of the recurring task to remove.
   *
   * @return  The recurring task that was removed, or <CODE>null</CODE> if there
   *          was no such recurring task.
   *
   * @throws  DirectoryException  If there is currently a pending or running
   *                              iteration of the associated recurring task.
   */
  public RecurringTask removeRecurringTask(String recurringTaskID)
         throws DirectoryException
  {
    schedulerLock.lock();

    try
    {
      RecurringTask recurringTask = recurringTasks.remove(recurringTaskID);
      HashMap<String,Task> iterationsMap = new HashMap<>();

      for (Task t : tasks.values())
      {
        // Find any existing task iterations and try to cancel them.
        if (t.getRecurringTaskID() != null &&
            t.getRecurringTaskID().equals(recurringTaskID))
        {
          TaskState state = t.getTaskState();
          if (!TaskState.isDone(state) && !TaskState.isCancelled(state))
          {
            cancelTask(t.getTaskID());
          }
          iterationsMap.put(t.getTaskID(), t);
        }
      }

      // Remove any completed task iterations.
      for (Map.Entry<String,Task> iterationEntry : iterationsMap.entrySet())
      {
        if (TaskState.isDone(iterationEntry.getValue().getTaskState()))
        {
          removeCompletedTask(iterationEntry.getKey());
        }
      }

      writeState();
      return recurringTask;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Schedules the provided task for execution.  If the scheduler is active and
   * the start time has arrived, then the task will begin execution immediately.
   * Otherwise, it will be placed in the pending queue to be started at the
   * appropriate time.
   *
   * @param  task        The task to be scheduled.
   * @param  writeState  Indicates whether the current state information for
   *                     the scheduler should be persisted to disk once the
   *                     task is scheduled.
   *
   * @throws  DirectoryException  If a problem occurs while trying to schedule
   *                              the task (e.g., there's already another task
   *                              defined with the same ID).
   */
  public void scheduleTask(Task task, boolean writeState)
         throws DirectoryException
  {
    schedulerLock.lock();


    try
    {
      String id = task.getTaskID();

      if (tasks.containsKey(id))
      {
        LocalizableMessage message = ERR_TASKSCHED_DUPLICATE_TASK_ID.get(id);
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
      }

      for (String dependencyID : task.getDependencyIDs())
      {
        Task t = tasks.get(dependencyID);
        if (t == null)
        {
          LocalizableMessage message = ERR_TASKSCHED_DEPENDENCY_MISSING.get(id, dependencyID);
          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
        }
      }

      tasks.put(id, task);

      TaskState state = shouldStart(task);
      task.setTaskState(state);

      if (state == TaskState.RUNNING)
      {
        TaskThread taskThread;
        if (idleThreads.isEmpty())
        {
          taskThread = new TaskThread(this, nextThreadID++);
          taskThread.start();
        }
        else
        {
          taskThread = idleThreads.removeFirst();
        }

        runningTasks.add(task);
        activeThreads.put(task.getTaskID(), taskThread);
        taskThread.setTask(task);
      }
      else if (TaskState.isDone(state))
      {
        if (state == TaskState.CANCELED_BEFORE_STARTING && task.isRecurring())
        {
          pendingTasks.add(task);
        }
        else
        {
          completedTasks.add(task);
        }
      }
      else
      {
        pendingTasks.add(task);
      }

      if (writeState)
      {
        writeState();
      }
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Attempts to cancel the task with the given task ID.  This will only cancel
   * the task if it has not yet started running.  If it has started, then it
   * will not be interrupted.
   *
   * @param  taskID  The task ID of the task to cancel.
   *
   * @return  The requested task, which may or may not have actually been
   *          cancelled (the task state should make it possible to determine
   *          whether it was cancelled), or <CODE>null</CODE> if there is no
   *          such task.
   */
  public Task cancelTask(String taskID)
  {
    schedulerLock.lock();

    try
    {
      Task t = tasks.get(taskID);
      if (t == null)
      {
        return null;
      }

      if (TaskState.isPending(t.getTaskState()))
      {
        pendingTasks.remove(t);
        t.setTaskState(TaskState.CANCELED_BEFORE_STARTING);
        addCompletedTask(t);
        writeState();
      }

      return t;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Removes the specified pending task.  It will be completely removed rather
   * than moving it to the set of completed tasks.
   *
   * @param  taskID  The task ID of the pending task to remove.
   *
   * @return  The task that was removed.
   *
   * @throws  DirectoryException  If the requested task is not in the pending
   *                              queue.
   */
  public Task removePendingTask(String taskID)
         throws DirectoryException
  {
    schedulerLock.lock();

    try
    {
      Task t = tasks.get(taskID);
      if (t == null)
      {
        LocalizableMessage message = ERR_TASKSCHED_REMOVE_PENDING_NO_SUCH_TASK.get(taskID);
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
      }

      if (TaskState.isPending(t.getTaskState()))
      {
        tasks.remove(taskID);
        pendingTasks.remove(t);
        writeState();
        return t;
      }
      else
      {
        LocalizableMessage message = ERR_TASKSCHED_REMOVE_PENDING_NOT_PENDING.get(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.
   *
   * @return  The task that was removed.
   *
   * @throws  DirectoryException  If the requested task could not be found.
   */
  public Task removeCompletedTask(String taskID)
         throws DirectoryException
  {
    schedulerLock.lock();

    try
    {
      Iterator<Task> iterator = completedTasks.iterator();
      while (iterator.hasNext())
      {
        Task t = iterator.next();
        if (t.getTaskID().equals(taskID))
        {
          iterator.remove();
          tasks.remove(taskID);
          writeState();
          return t;
        }
      }

      LocalizableMessage message = ERR_TASKSCHED_REMOVE_COMPLETED_NO_SUCH_TASK.get(taskID);
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Indicates that processing has completed on the provided task thread and
   * that it is now available for processing other tasks.  The thread may be
   * immediately used for processing another task if appropriate.
   *
   * @param  taskThread     The thread that has completed processing on its
   *                        previously-assigned task.
   * @param  completedTask  The task for which processing has been completed.
   * @param  taskState      The task state for this completed task.
   *
   * @return  <CODE>true</CODE> if the thread should continue running and
   *          wait for the next task to process, or <CODE>false</CODE> if it
   *          should exit immediately.
   */
  public boolean threadDone(TaskThread taskThread, Task completedTask,
    TaskState taskState)
  {
    schedulerLock.lock();

    try
    {
      completedTask.setCompletionTime(TimeThread.getTime());
      completedTask.setTaskState(taskState);
      addCompletedTask(completedTask);

      try
      {
        completedTask.sendNotificationEMailMessage();
      }
      catch (Exception e)
      {
        logger.traceException(e);
      }

      String taskID = completedTask.getTaskID();
      if (activeThreads.remove(taskID) == null)
      {
        return false;
      }

      // See if the task is part of a recurring task.
      // If so, then schedule the next iteration.
      scheduleNextRecurringTaskIteration(completedTask,
              new GregorianCalendar());
      writeState();

      if (isRunning)
      {
        idleThreads.add(taskThread);
        return true;
      }
      else
      {
        return false;
      }
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Check if a given task is a recurring task iteration and re-schedule it.
   * @param  completedTask  The task for which processing has been completed.
   * @param  calendar  The calendar date and time to schedule from.
   */
  protected void scheduleNextRecurringTaskIteration(Task completedTask,
          GregorianCalendar calendar)
  {
    String recurringTaskID = completedTask.getRecurringTaskID();
    if (recurringTaskID != null)
    {
      RecurringTask recurringTask = recurringTasks.get(recurringTaskID);
      if (recurringTask != null)
      {
        Task newIteration = null;
        try {
          newIteration = recurringTask.scheduleNextIteration(calendar);
        } catch (DirectoryException de) {
          logger.error(de.getMessageObject());
        }
        if (newIteration != null)
        {
          try
          {
            // If there is an existing task with the same id
            // and it is in completed state, take its place.
            Task t = tasks.get(newIteration.getTaskID());
            if (t != null && TaskState.isDone(t.getTaskState()))
            {
              removeCompletedTask(t.getTaskID());
            }

            scheduleTask(newIteration, 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)
            {
              logger.traceException(de);

              LocalizableMessage message =
                  ERR_TASKSCHED_ERROR_SCHEDULING_RECURRING_ITERATION.get(
                      recurringTaskID, de.getMessageObject());
              logger.error(message);

              DirectoryServer.sendAlertNotification(this,
                   ALERT_TYPE_CANNOT_SCHEDULE_RECURRING_ITERATION,
                      message);
            }
          }
        }
      }
    }
  }



  /**
   * Adds the provided task to the set of completed tasks associated with the
   * scheduler.  It will be automatically removed after the appropriate
   * retention time has elapsed.
   *
   * @param  completedTask  The task for which processing has completed.
   */
  public void addCompletedTask(Task completedTask)
  {
    // The scheduler lock is reentrant, so even if we already hold it, we can
    // acquire it again.
    schedulerLock.lock();

    try
    {
      completedTasks.add(completedTask);
      runningTasks.remove(completedTask);

      // If the task never ran set its completion
      // time here explicitly so that it can be
      // correctly evaluated for retention later.
      if (completedTask.getCompletionTime() == -1)
      {
        completedTask.setCompletionTime(TimeThread.getTime());
      }
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Stops the scheduler so that it will not start any scheduled tasks.  It will
   * not attempt to interrupt any tasks that are already running.  Note that
   * once the scheduler has been stopped, it cannot be restarted and it will be
   * necessary to restart the task backend to start a new scheduler instance.
   */
  public void stopScheduler()
  {
    stopRequested = true;

    try
    {
      schedulerThread.interrupt();
    }
    catch (Exception e)
    {
      logger.traceException(e);
    }

    try
    {
      schedulerThread.join();
    }
    catch (Exception e)
    {
      logger.traceException(e);
    }

    pendingTasks.clear();
    runningTasks.clear();
    completedTasks.clear();
    tasks.clear();

    for (TaskThread thread : idleThreads)
    {
      LocalizableMessage message = INFO_TASKBE_INTERRUPTED_BY_SHUTDOWN.get();
      thread.interruptTask(TaskState.STOPPED_BY_SHUTDOWN, message, true);
    }
  }



  /**
   * Attempts to interrupt any tasks that are actively running.  This will not
   * make any attempt to stop the scheduler.
   *
   * @param  interruptState   The state that should be assigned to the tasks if
   *                          they are successfully interrupted.
   * @param  interruptReason  A message indicating the reason that the tasks
   *                          are to be interrupted.
   * @param  waitForStop      Indicates whether this method should wait until
   *                          all active tasks have stopped before returning.
   */
  public void interruptRunningTasks(TaskState interruptState,
                                    LocalizableMessage interruptReason,
                                    boolean waitForStop)
  {
    // Grab a copy of the running threads so that we can operate on them without
    // holding the lock.
    LinkedList<TaskThread> threadList = new LinkedList<>();

    schedulerLock.lock();
    try
    {
      threadList.addAll(activeThreads.values());
    }
    finally
    {
      schedulerLock.unlock();
    }


    // Iterate through all the task threads and request that they stop
    // processing.
    for (TaskThread t : threadList)
    {
      try
      {
        t.interruptTask(interruptState, interruptReason, true);
      }
      catch (Exception e)
      {
        logger.traceException(e);
      }
    }


    // If we should actually wait for all the task threads to stop, then do so.
    if (waitForStop)
    {
      for (TaskThread t : threadList)
      {
        try
        {
          t.join();
        }
        catch (Exception e)
        {
          logger.traceException(e);
        }
      }
    }
  }



  /**
   * Operates in a loop, launching tasks at the appropriate time and performing
   * any necessary periodic cleanup.
   */
  @Override
  public void run()
  {
    isRunning       = true;
    schedulerThread = currentThread();

    try
    {
      while (! stopRequested)
      {
        schedulerLock.lock();

        boolean writeState = false;
        long sleepTime = MAX_SLEEP_TIME;

        try
        {
          // If there are any pending tasks that need to be started, then do so
          // now.
          Iterator<Task> iterator = pendingTasks.iterator();
          while (iterator.hasNext())
          {
            Task t = iterator.next();
            TaskState state = shouldStart(t);

            if (state == TaskState.RUNNING)
            {
              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);

              iterator.remove();
              writeState = true;
            }
            else if (state == TaskState.WAITING_ON_START_TIME)
            {
              // 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);
            }
            // 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())
            {
              t.setTaskState(state);
              writeState = true;
            }
          }


          // Clean up any completed tasks that have been around long enough.
          long retentionTimeMillis =
            TimeUnit.SECONDS.toMillis(taskBackend.getRetentionTime());
          long oldestRetainedCompletionTime =
                    TimeThread.getTime() - retentionTimeMillis;
          iterator = completedTasks.iterator();
          while (iterator.hasNext())
          {
            Task t = iterator.next();
            if (t.getCompletionTime() < oldestRetainedCompletionTime)
            {
              iterator.remove();
              tasks.remove(t.getTaskID());
              writeState = true;
            }
          }

          // If anything changed, then make sure that the on-disk state gets
          // updated.
          if (writeState)
          {
            writeState();
          }
        }
        finally
        {
          schedulerLock.unlock();
        }


        try
        {
          if (sleepTime > 0)
          {
            Thread.sleep(sleepTime);
          }
        } catch (InterruptedException ie) {}
      }
    }
    finally
    {
      isRunning = false;
    }
  }



  /**
   * Determines whether the specified task should start running.  This is based
   * on the start time, the set of dependencies, and whether or not the
   * scheduler is active.  Note that the caller to this method must hold the
   * scheduler lock.
   *
   * @param  task  The task for which to make the determination.
   *
   * @return  The task state that should be used for the task.  It should be
   *          RUNNING if the task should be started, or some other state if not.
   */
  private TaskState shouldStart(Task task)
  {
    // If the task has finished we don't want to restart it
    TaskState state = task.getTaskState();

    // Reset task state if recurring.
    if (state == TaskState.RECURRING) {
      state = null;
    }

    if (state != null && TaskState.isDone(state))
    {
      return state;
    }

    if (! isRunning)
    {
      return TaskState.UNSCHEDULED;
    }

    if (task.getScheduledStartTime() > TimeThread.getTime())
    {
      return TaskState.WAITING_ON_START_TIME;
    }

    LinkedList<String> dependencyIDs = task.getDependencyIDs();
    if (dependencyIDs != null)
    {
      for (String dependencyID : dependencyIDs)
      {
        Task t = tasks.get(dependencyID);
        if (t != null)
        {
          TaskState tState = t.getTaskState();
          if (!TaskState.isDone(tState))
          {
            return TaskState.WAITING_ON_DEPENDENCY;
          }
          if (!TaskState.isSuccessful(tState))
          {
            FailedDependencyAction action = task.getFailedDependencyAction();
            switch (action)
            {
              case CANCEL:
                cancelTask(task.getTaskID());
                return task.getTaskState();
              case DISABLE:
                task.setTaskState(TaskState.DISABLED);
                return task.getTaskState();
              default:
                break;
            }
          }
        }
      }
    }

    return TaskState.RUNNING;
  }



  /**
   * Populates the scheduler with information read from the task backing file.
   * If no backing file is found, then create a new one.  The caller must
   * already hold the scheduler lock or otherwise ensure that this is a
   * threadsafe operation.
   *
   * @throws  InitializationException  If a fatal error occurs while attempting
   *                                   to perform the initialization.
   */
  private void initializeTasksFromBackingFile()
          throws InitializationException
  {
    String backingFilePath = taskBackend.getTaskBackingFile();

    try
    {
      File backingFile = getFileForPath(backingFilePath);
      if (! backingFile.exists())
      {
        createNewTaskBackingFile();
        return;
      }


      LDIFImportConfig importConfig = new LDIFImportConfig(backingFilePath);
      LDIFReader ldifReader = new LDIFReader(importConfig);

      taskRootEntry            = null;
      recurringTaskParentEntry = null;
      scheduledTaskParentEntry = null;

      while (true)
      {
        Entry entry;

        try
        {
          entry = ldifReader.readEntry();
        }
        catch (LDIFException le)
        {
          logger.traceException(le);

          if (le.canContinueReading())
          {
            logger.error(ERR_TASKSCHED_CANNOT_PARSE_ENTRY_RECOVERABLE,
                backingFilePath, le.getLineNumber(), le.getMessage());

            continue;
          }
          else
          {
            try
            {
              ldifReader.close();
            }
            catch (Exception e)
            {
              logger.traceException(e);
            }

            LocalizableMessage message = ERR_TASKSCHED_CANNOT_PARSE_ENTRY_FATAL.get(
                backingFilePath, le.getLineNumber(), le.getMessage());
            throw new InitializationException(message);
          }
        }

        if (entry == null)
        {
          break;
        }

        DN entryDN = entry.getName();
        if (entryDN.equals(taskBackend.getTaskRootDN()))
        {
          taskRootEntry = entry;
        }
        else if (entryDN.equals(taskBackend.getRecurringTasksParentDN()))
        {
          recurringTaskParentEntry = entry;
        }
        else if (entryDN.equals(taskBackend.getScheduledTasksParentDN()))
        {
          scheduledTaskParentEntry = entry;
        }
        else
        {
          DN parentDN = entryDN.getParentDNInSuffix();
          if (parentDN == null)
          {
            logger.error(ERR_TASKSCHED_ENTRY_HAS_NO_PARENT, entryDN, taskBackend.getTaskRootDN());
          }
          else if (parentDN.equals(taskBackend.getScheduledTasksParentDN()))
          {
            try
            {
              Task task = entryToScheduledTask(entry, null);
              if (TaskState.isDone(task.getTaskState()))
              {
                String id = task.getTaskID();
                if (tasks.containsKey(id))
                {
                  logger.warn(WARN_TASKSCHED_DUPLICATE_TASK_ID, id);
                }
                else
                {
                  completedTasks.add(task);
                  tasks.put(id, task);
                }
              }
              else
              {
                scheduleTask(task, false);
              }
            }
            catch (DirectoryException de)
            {
              logger.traceException(de);
              logger.error(ERR_TASKSCHED_CANNOT_SCHEDULE_TASK_FROM_ENTRY, entryDN, de.getMessageObject());
            }
          }
          else if (parentDN.equals(taskBackend.getRecurringTasksParentDN()))
          {
            try
            {
              RecurringTask recurringTask = entryToRecurringTask(entry);
              addRecurringTask(recurringTask, false);
            }
            catch (DirectoryException de)
            {
              logger.traceException(de);
              logger.error(ERR_TASKSCHED_CANNOT_SCHEDULE_RECURRING_TASK_FROM_ENTRY, entryDN, de.getMessageObject());
            }
          }
          else
          {
            logger.error(ERR_TASKSCHED_INVALID_TASK_ENTRY_DN, entryDN, backingFilePath);
          }
        }
      }

      ldifReader.close();
    }
    catch (IOException ioe)
    {
      logger.traceException(ioe);

      LocalizableMessage message = ERR_TASKSCHED_ERROR_READING_TASK_BACKING_FILE.get(
          backingFilePath, stackTraceToSingleLineString(ioe));
      throw new InitializationException(message, ioe);
    }
  }



  /**
   * Creates a new task backing file that contains only the basic structure but
   * no scheduled or recurring task entries.  The caller must already hold the
   * scheduler lock or otherwise ensure that this is a threadsafe operation.
   *
   * @throws  InitializationException  If a problem occurs while attempting to
   *                                   create the backing file.
   */
  private void createNewTaskBackingFile()
          throws InitializationException
  {
    String backingFile = taskBackend.getTaskBackingFile();
    LDIFExportConfig exportConfig =
         new LDIFExportConfig(backingFile, ExistingFileBehavior.OVERWRITE);

    try
    {
      LDIFWriter writer = new LDIFWriter(exportConfig);

      // First, write a header to the top of the file to indicate that it should
      // not be manually edited.
      writer.writeComment(INFO_TASKBE_BACKING_FILE_HEADER.get(), 80);


      // Next, create the required hierarchical entries and add them to the
      // LDIF.
      taskRootEntry = createEntry(taskBackend.getTaskRootDN());
      writer.writeEntry(taskRootEntry);

      scheduledTaskParentEntry =
           createEntry(taskBackend.getScheduledTasksParentDN());
      writer.writeEntry(scheduledTaskParentEntry);

      recurringTaskParentEntry =
           createEntry(taskBackend.getRecurringTasksParentDN());
      writer.writeEntry(recurringTaskParentEntry);


      // Close the file and we're done.
      writer.close();
    }
    catch (IOException ioe)
    {
      logger.traceException(ioe);

      LocalizableMessage message = ERR_TASKSCHED_CANNOT_CREATE_BACKING_FILE.get(
          backingFile, stackTraceToSingleLineString(ioe));
      throw new InitializationException(message, ioe);
    }
    catch (LDIFException le)
    {
      logger.traceException(le);
      LocalizableMessage message = ERR_TASKSCHED_CANNOT_CREATE_BACKING_FILE.get(
          backingFile, le.getMessage());
      throw new InitializationException(message, le);
    }
  }



  /**
   * Writes state information about all tasks and recurring tasks to disk.
   */
  public void writeState()
  {
    String backingFilePath = taskBackend.getTaskBackingFile();
    String tmpFilePath     = backingFilePath + ".tmp";
    LDIFExportConfig exportConfig =
         new LDIFExportConfig(tmpFilePath, ExistingFileBehavior.OVERWRITE);


    schedulerLock.lock();

    try
    {
      LDIFWriter writer = new LDIFWriter(exportConfig);

      // First, write a header to the top of the file to indicate that it should
      // not be manually edited.
      writer.writeComment(INFO_TASKBE_BACKING_FILE_HEADER.get(), 80);


      // Next, write the structural entries to the top of the LDIF.
      writer.writeEntry(taskRootEntry);
      writer.writeEntry(scheduledTaskParentEntry);
      writer.writeEntry(recurringTaskParentEntry);


      // Iterate through all the recurring tasks and write them to LDIF.
      for (RecurringTask recurringTask : recurringTasks.values())
      {
        writer.writeEntry(recurringTask.getRecurringTaskEntry());
      }


      // Iterate through all the scheduled tasks and write them to LDIF.
      for (Task task : tasks.values())
      {
        writer.writeEntry(task.getTaskEntry());
      }


      // Close the file.
      writer.close();


      // See if there is a ".save" file.  If so, then delete it.
      File saveFile = getFileForPath(backingFilePath + ".save");
      try
      {
        if (saveFile.exists())
        {
          saveFile.delete();
        }
      }
      catch (Exception e)
      {
        logger.traceException(e);
      }


      // If there is an existing backing file, then rename it to ".save".
      File backingFile = getFileForPath(backingFilePath);
      try
      {
        if (backingFile.exists())
        {
          backingFile.renameTo(saveFile);
        }
      }
      catch (Exception e)
      {
        logger.traceException(e);

        LocalizableMessage message = WARN_TASKSCHED_CANNOT_RENAME_CURRENT_BACKING_FILE.get(
            backingFilePath, saveFile.getAbsolutePath(), stackTraceToSingleLineString(e));
        logger.warn(message);
        DirectoryServer.sendAlertNotification(
            this, ALERT_TYPE_CANNOT_RENAME_CURRENT_TASK_FILE, message);
      }


      // Rename the ".tmp" file into place.
      File tmpFile = getFileForPath(tmpFilePath);
      try
      {
        tmpFile.renameTo(backingFile);
      }
      catch (Exception e)
      {
        logger.traceException(e);

        LocalizableMessage message = ERR_TASKSCHED_CANNOT_RENAME_NEW_BACKING_FILE.get(
            tmpFilePath, backingFilePath, stackTraceToSingleLineString(e));
        logger.error(message);
        DirectoryServer.sendAlertNotification(
            this, ALERT_TYPE_CANNOT_RENAME_NEW_TASK_FILE, message);
      }
    }
    catch (LDIFException le)
    {
      logger.traceException(le);
      LocalizableMessage message =
          ERR_TASKSCHED_CANNOT_WRITE_BACKING_FILE.get(tmpFilePath, le
              .getMessage());
      logger.error(message);
      DirectoryServer.sendAlertNotification(this,
                           ALERT_TYPE_CANNOT_WRITE_TASK_FILE, message);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          ERR_TASKSCHED_CANNOT_WRITE_BACKING_FILE.get(tmpFilePath,
              stackTraceToSingleLineString(e));
      logger.error(message);
      DirectoryServer.sendAlertNotification(this,
                           ALERT_TYPE_CANNOT_WRITE_TASK_FILE, message);
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the total number of entries in the task backend.
   *
   * @return  The total number of entries in the task backend.
   */
  public long getEntryCount()
  {
    schedulerLock.lock();

    try
    {
      return tasks.size() + recurringTasks.size() + 3;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }

  /**
   * Retrieves the number of scheduled tasks in the task backend.
   *
   * @return  The total number of entries in the task backend.
   */
  public long getScheduledTaskCount()
  {
    schedulerLock.lock();

    try
    {
      return tasks.size();
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the number of recurring tasks in the task backend.
   *
   * @return  The total number of entries in the task backend.
   */
  public long getRecurringTaskCount()
  {
    schedulerLock.lock();

    try
    {
      return recurringTasks.size();
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the task backend with which this scheduler is associated.
   *
   * @return  The task backend with which this scheduler is associated.
   */
  public TaskBackend getTaskBackend()
  {
    return taskBackend;
  }



  /**
   * Retrieves the root entry that is the common ancestor for all entries in the
   * task backend.
   *
   * @return  The root entry that is the common ancestor for all entries in the
   *          task backend.
   */
  public Entry getTaskRootEntry()
  {
    return taskRootEntry.duplicate(true);
  }



  /**
   * Retrieves the entry that is the immediate parent for all scheduled task
   * entries in the task backend.
   *
   * @return  The entry that is the immediate parent for all scheduled task
   *          entries in the task backend.
   */
  public Entry getScheduledTaskParentEntry()
  {
    return scheduledTaskParentEntry.duplicate(true);
  }



  /**
   * Retrieves the entry that is the immediate parent for all recurring task
   * entries in the task backend.
   *
   * @return  The entry that is the immediate parent for all recurring task
   *          entries in the task backend.
   */
  public Entry getRecurringTaskParentEntry()
  {
    return recurringTaskParentEntry.duplicate(true);
  }



  /**
   * Retrieves the scheduled task with the given task ID.
   *
   * @param  taskID  The task ID for the scheduled task to retrieve.
   *
   * @return  The requested scheduled task, or <CODE>null</CODE> if there is no
   *          such task.
   */
  public Task getScheduledTask(String taskID)
  {
    schedulerLock.lock();

    try
    {
      return tasks.get(taskID);
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the scheduled task created from the specified entry.
   *
   * @param  taskEntryDN  The DN of the task configuration entry associated
   *                       with the task to retrieve.
   *
   * @return  The requested scheduled task, or <CODE>null</CODE> if there is no
   *          such task.
   */
  public Task getScheduledTask(DN taskEntryDN)
  {
    schedulerLock.lock();

    try
    {
      for (Task t : tasks.values())
      {
        if (taskEntryDN.equals(t.getTaskEntry().getName()))
        {
          return t;
        }
      }

      return null;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Indicates whether the current thread already holds a lock on the scheduler.
   *
   * @return  {@code true} if the current thread holds the scheduler lock, or
   *          {@code false} if not.
   */
  boolean holdsSchedulerLock()
  {
    return schedulerLock.isHeldByCurrentThread();
  }



  /**
   * Attempts to acquire a write lock on the specified entry, trying as many
   * times as necessary until the lock has been acquired.
   *
   * @param  entryDN  The DN of the entry for which to acquire the write lock.
   *
   * @return  The write lock that has been acquired for the entry.
   */
  DNLock writeLockEntry(DN entryDN)
  {
    DNLock lock = null;
    while (lock == null)
    {
      lock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
    }
    return lock;
  }



  /**
   * Attempts to acquire a read lock on the specified entry.
   *
   * @param  entryDN  The DN of the entry for which to acquire the read lock.
   *
   * @return  The read lock that has been acquired for the entry.
   *
   * @throws  DirectoryException  If the read lock cannot be acquired.
   */
  DNLock readLockEntry(DN entryDN) throws DirectoryException
  {
    final DNLock lock = DirectoryServer.getLockManager().tryReadLockEntry(entryDN);
    if (lock != null)
    {
      return lock;
    }
    throw new DirectoryException(ResultCode.BUSY, ERR_BACKEND_CANNOT_LOCK_ENTRY.get(entryDN));
  }



  /**
   * Retrieves the scheduled task entry with the provided DN.  The caller should
   * hold a read lock on the target entry.
   *
   * @param  scheduledTaskEntryDN  The entry DN that indicates which scheduled
   *                               task entry to retrieve.
   *
   * @return  The scheduled task entry with the provided DN, or
   *          <CODE>null</CODE> if no scheduled task has the provided DN.
   */
  public Entry getScheduledTaskEntry(DN scheduledTaskEntryDN)
  {
    schedulerLock.lock();

    try
    {
      for (Task task : tasks.values())
      {
        Entry taskEntry = task.getTaskEntry();

        if (scheduledTaskEntryDN.equals(taskEntry.getName()))
        {
          return taskEntry.duplicate(true);
        }
      }

      return null;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Compares the filter in the provided search operation against each of the
   * task entries, returning any that match.  Note that only the search filter
   * will be used -- the base and scope will be ignored, so the caller must
   * ensure that they are correct for scheduled tasks.
   *
   * @param  searchOperation  The search operation to use when performing the
   *                          search.
   *
   * @return  <CODE>true</CODE> if processing should continue on the search
   *          operation, or <CODE>false</CODE> if it should not for some reason
   *          (e.g., a size or time limit was reached).
   *
   * @throws  DirectoryException  If a problem occurs while processing the
   *                              search operation against the scheduled tasks.
   */
  public boolean searchScheduledTasks(SearchOperation searchOperation)
         throws DirectoryException
  {
    SearchFilter filter = searchOperation.getFilter();

    schedulerLock.lock();

    try
    {
      for (Task t : tasks.values())
      {
        DN taskEntryDN = t.getTaskEntryDN();
        DNLock lock = readLockEntry(taskEntryDN);
        try
        {
          Entry e = t.getTaskEntry().duplicate(true);
          if (filter.matchesEntry(e) && !searchOperation.returnEntry(e, null))
          {
            return false;
          }
        }
        finally
        {
          lock.unlock();
        }
      }

      return true;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the recurring task with the given recurring task ID.
   *
   * @param  recurringTaskID  The recurring task ID for the recurring task to
   *                          retrieve.
   *
   * @return  The requested recurring task, or <CODE>null</CODE> if there is no
   *          such recurring task.
   */
  public RecurringTask getRecurringTask(String recurringTaskID)
  {
    schedulerLock.lock();

    try
    {
      return recurringTasks.get(recurringTaskID);
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the recurring task with the given recurring task ID.
   *
   * @param  recurringTaskEntryDN  The recurring task ID for the recurring task
   *                               to retrieve.
   *
   * @return  The requested recurring task, or <CODE>null</CODE> if there is no
   *          such recurring task.
   */
  public RecurringTask getRecurringTask(DN recurringTaskEntryDN)
  {
    schedulerLock.lock();

    try
    {
      for (RecurringTask rt : recurringTasks.values())
      {
        if (recurringTaskEntryDN.equals(rt.getRecurringTaskEntry().getName()))
        {
          return rt;
        }
      }

      return null;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Retrieves the recurring task entry with the provided DN.  The caller should
   * hold a read lock on the target entry.
   *
   * @param  recurringTaskEntryDN  The entry DN that indicates which recurring
   *                               task entry to retrieve.
   *
   * @return  The recurring task entry with the provided DN, or
   *          <CODE>null</CODE> if no recurring task has the provided DN.
   */
  public Entry getRecurringTaskEntry(DN recurringTaskEntryDN)
  {
    schedulerLock.lock();

    try
    {
      for (RecurringTask recurringTask : recurringTasks.values())
      {
        Entry recurringTaskEntry = recurringTask.getRecurringTaskEntry();

        if (recurringTaskEntryDN.equals(recurringTaskEntry.getName()))
        {
          return recurringTaskEntry.duplicate(true);
        }
      }

      return null;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Compares the filter in the provided search operation against each of the
   * recurring task entries, returning any that match.  Note that only the
   * search filter will be used -- the base and scope will be ignored, so the
   * caller must ensure that they are correct for recurring tasks.
   *
   * @param  searchOperation  The search operation to use when performing the
   *                          search.
   *
   * @return  <CODE>true</CODE> if processing should continue on the search
   *          operation, or <CODE>false</CODE> if it should not for some reason
   *          (e.g., a size or time limit was reached).
   *
   * @throws  DirectoryException  If a problem occurs while processing the
   *                              search operation against the recurring tasks.
   */
  public boolean searchRecurringTasks(SearchOperation searchOperation)
         throws DirectoryException
  {
    SearchFilter filter = searchOperation.getFilter();

    schedulerLock.lock();

    try
    {
      for (RecurringTask rt : recurringTasks.values())
      {
        DN recurringTaskEntryDN = rt.getRecurringTaskEntryDN();
        DNLock lock = readLockEntry(recurringTaskEntryDN);
        try
        {
          Entry e = rt.getRecurringTaskEntry().duplicate(true);
          if (filter.matchesEntry(e) && ! searchOperation.returnEntry(e, null))
          {
            return false;
          }
        }
        finally
        {
          lock.unlock();
        }
      }

      return true;
    }
    finally
    {
      schedulerLock.unlock();
    }
  }



  /**
   * Decodes the contents of the provided entry as a scheduled task.  The
   * resulting task will not actually be scheduled for processing.
   *
   * @param  entry      The entry to decode as a scheduled task.
   * @param  operation  The operation used to create this task in the server, or
   *                    {@code null} if the operation is not available.
   *
   * @return  The scheduled task decoded from the provided entry.
   *
   * @throws  DirectoryException  If the provided entry cannot be decoded as a
   *                              scheduled task.
   */
  public Task entryToScheduledTask(Entry entry, Operation operation)
         throws DirectoryException
  {
    // Get the name of the class that implements the task logic.
    AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(ATTR_TASK_CLASS.toLowerCase(), ATTR_TASK_CLASS);
    List<Attribute> attrList = entry.getAttribute(attrType);
    if (attrList == null || attrList.isEmpty())
    {
      LocalizableMessage message = ERR_TASKSCHED_NO_CLASS_ATTRIBUTE.get(ATTR_TASK_ID);
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    if (attrList.size() > 1)
    {
      LocalizableMessage message = ERR_TASKSCHED_MULTIPLE_CLASS_TYPES.get(ATTR_TASK_ID);
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    Attribute attr = attrList.get(0);
    if (attr.isEmpty())
    {
      LocalizableMessage message = ERR_TASKSCHED_NO_CLASS_VALUES.get(ATTR_TASK_ID);
      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
    }

    Iterator<ByteString> iterator = attr.iterator();
    ByteString value = iterator.next();
    if (iterator.hasNext())
    {
      LocalizableMessage message = ERR_TASKSCHED_MULTIPLE_CLASS_VALUES.get(ATTR_TASK_ID);
      throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message);
    }

    // Try to load the specified class.
    String taskClassName = value.toString();
    Class<?> taskClass;
    try
    {
      taskClass = DirectoryServer.loadClass(taskClassName);
    }
    catch (Exception e)
    {
      logger.traceException(e);

      LocalizableMessage message = ERR_TASKSCHED_CANNOT_LOAD_CLASS.
          get(taskClassName, ATTR_TASK_CLASS, stackTraceToSingleLineString(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    // Instantiate the class as a task.
    Task task;
    try
    {
      task = (Task) taskClass.newInstance();
    }
    catch (Exception e)
    {
      logger.traceException(e);

      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INSTANTIATE_CLASS_AS_TASK.get(
          taskClassName, Task.class.getName());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    // Perform the necessary internal and external initialization for the task.
    try
    {
      task.initializeTaskInternal(serverContext, this, entry);
    }
    catch (InitializationException ie)
    {
      logger.traceException(ie);

      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INITIALIZE_INTERNAL.get(
          taskClassName, ie.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INITIALIZE_INTERNAL.get(
          taskClassName, stackTraceToSingleLineString(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
    }

    if (!TaskState.isDone(task.getTaskState()) &&
        !DirectoryServer.getAllowedTasks().contains(taskClassName))
    {
      LocalizableMessage message = ERR_TASKSCHED_NOT_ALLOWED_TASK.get(taskClassName);
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }

    task.setOperation(operation);

    // Avoid task specific initialization for completed tasks.
    if (!TaskState.isDone(task.getTaskState())) {
      task.initializeTask();
    }
    task.setOperation(null);

    return task;
  }



  /**
   * Decodes the contents of the provided entry as a recurring task.  The
   * resulting recurring task will not actually be added to the scheduler.
   *
   * @param  entry  The entry to decode as a recurring task.
   *
   * @return  The recurring task decoded from the provided entry.
   *
   * @throws  DirectoryException  If the provided entry cannot be decoded as a
   *                              recurring task.
   */
  public RecurringTask entryToRecurringTask(Entry entry)
         throws DirectoryException
  {
    return new RecurringTask(serverContext, this, entry);
  }



  /**
   * Retrieves the DN of the configuration entry with which this alert generator
   * is associated.
   *
   * @return  The DN of the configuration entry with which this alert generator
   *          is associated.
   */
  @Override
  public DN getComponentEntryDN()
  {
    return taskBackend.getConfigEntryDN();
  }



  /**
   * Retrieves the fully-qualified name of the Java class for this alert
   * generator implementation.
   *
   * @return  The fully-qualified name of the Java class for this alert
   *          generator implementation.
   */
  @Override
  public String getClassName()
  {
    return CLASS_NAME;
  }



  /**
   * Retrieves information about the set of alerts that this generator may
   * produce.  The map returned should be between the notification type for a
   * particular notification and the human-readable description for that
   * notification.  This alert generator must not generate any alerts with types
   * that are not contained in this list.
   *
   * @return  Information about the set of alerts that this generator may
   *          produce.
   */
  @Override
  public LinkedHashMap<String,String> getAlerts()
  {
    LinkedHashMap<String, String> alerts = new LinkedHashMap<>();

    alerts.put(ALERT_TYPE_CANNOT_SCHEDULE_RECURRING_ITERATION,
               ALERT_DESCRIPTION_CANNOT_SCHEDULE_RECURRING_ITERATION);
    alerts.put(ALERT_TYPE_CANNOT_RENAME_CURRENT_TASK_FILE,
               ALERT_DESCRIPTION_CANNOT_RENAME_CURRENT_TASK_FILE);
    alerts.put(ALERT_TYPE_CANNOT_RENAME_NEW_TASK_FILE,
               ALERT_DESCRIPTION_CANNOT_RENAME_NEW_TASK_FILE);
    alerts.put(ALERT_TYPE_CANNOT_WRITE_TASK_FILE,
               ALERT_DESCRIPTION_CANNOT_WRITE_TASK_FILE);

    return alerts;
  }
}
