001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.backends.task;
018
019import static org.opends.messages.BackendMessages.*;
020import static org.opends.server.config.ConfigConstants.*;
021import static org.opends.server.util.CollectionUtils.*;
022import static org.opends.server.util.ServerConstants.*;
023import static org.opends.server.util.StaticUtils.*;
024
025import java.io.File;
026import java.io.IOException;
027import java.util.GregorianCalendar;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.LinkedHashMap;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034import java.util.TreeSet;
035import java.util.concurrent.TimeUnit;
036import java.util.concurrent.locks.ReentrantLock;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.ldap.ByteString;
041import org.forgerock.opendj.ldap.DN;
042import org.forgerock.opendj.ldap.ResultCode;
043import org.forgerock.opendj.ldap.schema.AttributeType;
044import org.opends.server.api.AlertGenerator;
045import org.opends.server.api.DirectoryThread;
046import org.opends.server.core.DirectoryServer;
047import org.opends.server.core.SearchOperation;
048import org.opends.server.core.ServerContext;
049import org.opends.server.types.Attribute;
050import org.opends.server.types.Attributes;
051import org.opends.server.types.DirectoryException;
052import org.opends.server.types.Entry;
053import org.opends.server.types.ExistingFileBehavior;
054import org.opends.server.types.InitializationException;
055import org.opends.server.types.LDIFExportConfig;
056import org.opends.server.types.LDIFImportConfig;
057import org.opends.server.types.LockManager.DNLock;
058import org.opends.server.types.Operation;
059import org.opends.server.types.SearchFilter;
060import org.opends.server.util.LDIFException;
061import org.opends.server.util.LDIFReader;
062import org.opends.server.util.LDIFWriter;
063import org.opends.server.util.TimeThread;
064
065/**
066 * This class defines a task scheduler for the Directory Server that will
067 * control the execution of scheduled tasks and other administrative functions
068 * that need to occur on a regular basis.
069 */
070public class TaskScheduler
071       extends DirectoryThread
072       implements AlertGenerator
073{
074  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
075
076  /**
077   * The fully-qualified name of this class.
078   */
079  private static final String CLASS_NAME =
080       "org.opends.server.backends.task.TaskScheduler";
081
082
083
084  /**
085   * The maximum length of time in milliseconds to sleep between iterations
086   * through the scheduler loop.
087   */
088  private static long MAX_SLEEP_TIME = 5000;
089
090
091  /** Indicates whether the scheduler is currently running. */
092  private boolean isRunning;
093  /** Indicates whether a request has been received to stop the scheduler. */
094  private boolean stopRequested;
095
096  /** The entry that serves as the immediate parent for recurring tasks. */
097  private Entry recurringTaskParentEntry;
098  /** The entry that serves as the immediate parent for scheduled tasks. */
099  private Entry scheduledTaskParentEntry;
100  /** The top-level entry at the root of the task tree. */
101  private Entry taskRootEntry;
102
103  /** The set of recurring tasks defined in the server. */
104  private final HashMap<String,RecurringTask> recurringTasks = new HashMap<>();
105  /** The set of tasks associated with this scheduler. */
106  private final HashMap<String,Task> tasks = new HashMap<>();
107  /** The set of worker threads that are actively busy processing tasks. */
108  private final HashMap<String,TaskThread> activeThreads = new HashMap<>();
109
110  /** The thread ID for the next task thread to be created;. */
111  private int nextThreadID = 1;
112
113  /** The set of worker threads that may be used to process tasks. */
114  private final LinkedList<TaskThread> idleThreads = new LinkedList<>();
115
116  /** The lock used to provide threadsafe access to the scheduler. */
117  private final ReentrantLock schedulerLock = new ReentrantLock();
118
119  /** The task backend with which this scheduler is associated. */
120  private TaskBackend taskBackend;
121
122  /** The thread being used to actually run the scheduler. */
123  private Thread schedulerThread;
124
125  /** The set of recently-completed tasks that need to be retained. */
126  private final TreeSet<Task> completedTasks = new TreeSet<>();
127  /** The set of tasks that have been scheduled but not yet arrived. */
128  private final TreeSet<Task> pendingTasks = new TreeSet<>();
129  /** The set of tasks that are currently running. */
130  private final TreeSet<Task> runningTasks = new TreeSet<>();
131
132  private ServerContext serverContext;
133
134  /**
135   * Creates a new task scheduler that will be used to ensure that tasks are
136   * invoked at the appropriate times.
137   * @param serverContext
138   *            The server context
139   * @param  taskBackend  The task backend with which this scheduler is
140   *                      associated.
141   *
142   * @throws  InitializationException  If a problem occurs while initializing
143   *                                   the scheduler from the backing file.
144   */
145  public TaskScheduler(ServerContext serverContext, TaskBackend taskBackend)
146         throws InitializationException
147  {
148    super("Task Scheduler Thread");
149
150    this.serverContext = serverContext;
151    this.taskBackend = taskBackend;
152
153    DirectoryServer.registerAlertGenerator(this);
154
155    initializeTasksFromBackingFile();
156
157    for (RecurringTask recurringTask : recurringTasks.values()) {
158      Task task = null;
159      try {
160        task = recurringTask.scheduleNextIteration(new GregorianCalendar());
161      } catch (DirectoryException de) {
162        logger.error(de.getMessageObject());
163      }
164      if (task != null) {
165        try {
166          scheduleTask(task, false);
167        } catch (DirectoryException de) {
168          // This task might have been already scheduled from before
169          // and thus got initialized from backing file, otherwise
170          // log error and continue.
171          if (de.getResultCode() != ResultCode.ENTRY_ALREADY_EXISTS) {
172            logger.error(de.getMessageObject());
173          }
174        }
175      }
176    }
177  }
178
179
180
181  /**
182   * Adds a recurring task to the scheduler, optionally scheduling the first
183   * iteration for processing.
184   *
185   * @param  recurringTask      The recurring task to add to the scheduler.
186   * @param  scheduleIteration  Indicates whether to schedule an iteration of
187   *                            the recurring task.
188   *
189   * @throws  DirectoryException  If a problem occurs while trying to add the
190   *                              recurring task (e.g., there's already another
191   *                              recurring task defined with the same ID).
192   */
193  public void addRecurringTask(RecurringTask recurringTask,
194                               boolean scheduleIteration)
195         throws DirectoryException
196  {
197    schedulerLock.lock();
198
199    try
200    {
201      String id = recurringTask.getRecurringTaskID();
202
203      if (recurringTasks.containsKey(id))
204      {
205        LocalizableMessage message =
206            ERR_TASKSCHED_DUPLICATE_RECURRING_ID.get(id);
207        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
208      }
209
210      Attribute attr = Attributes.create(ATTR_TASK_STATE, TaskState.RECURRING.toString());
211      Entry recurringTaskEntry = recurringTask.getRecurringTaskEntry();
212      recurringTaskEntry.putAttribute(attr.getAttributeDescription().getAttributeType(), newArrayList(attr));
213
214      if (scheduleIteration)
215      {
216        Task task = recurringTask.scheduleNextIteration(
217                new GregorianCalendar());
218        if (task != null)
219        {
220          // If there is an existing task with the same id
221          // and it is in completed state, take its place.
222          Task t = tasks.get(task.getTaskID());
223          if (t != null && TaskState.isDone(t.getTaskState()))
224          {
225            removeCompletedTask(t.getTaskID());
226          }
227
228          scheduleTask(task, false);
229        }
230      }
231
232      recurringTasks.put(id, recurringTask);
233      writeState();
234    }
235    finally
236    {
237      schedulerLock.unlock();
238    }
239  }
240
241
242
243  /**
244   * Removes the recurring task with the given ID.
245   *
246   * @param  recurringTaskID  The ID of the recurring task to remove.
247   *
248   * @return  The recurring task that was removed, or <CODE>null</CODE> if there
249   *          was no such recurring task.
250   *
251   * @throws  DirectoryException  If there is currently a pending or running
252   *                              iteration of the associated recurring task.
253   */
254  public RecurringTask removeRecurringTask(String recurringTaskID)
255         throws DirectoryException
256  {
257    schedulerLock.lock();
258
259    try
260    {
261      RecurringTask recurringTask = recurringTasks.remove(recurringTaskID);
262      HashMap<String,Task> iterationsMap = new HashMap<>();
263
264      for (Task t : tasks.values())
265      {
266        // Find any existing task iterations and try to cancel them.
267        if (t.getRecurringTaskID() != null &&
268            t.getRecurringTaskID().equals(recurringTaskID))
269        {
270          TaskState state = t.getTaskState();
271          if (!TaskState.isDone(state) && !TaskState.isCancelled(state))
272          {
273            cancelTask(t.getTaskID());
274          }
275          iterationsMap.put(t.getTaskID(), t);
276        }
277      }
278
279      // Remove any completed task iterations.
280      for (Map.Entry<String,Task> iterationEntry : iterationsMap.entrySet())
281      {
282        if (TaskState.isDone(iterationEntry.getValue().getTaskState()))
283        {
284          removeCompletedTask(iterationEntry.getKey());
285        }
286      }
287
288      writeState();
289      return recurringTask;
290    }
291    finally
292    {
293      schedulerLock.unlock();
294    }
295  }
296
297
298
299  /**
300   * Schedules the provided task for execution.  If the scheduler is active and
301   * the start time has arrived, then the task will begin execution immediately.
302   * Otherwise, it will be placed in the pending queue to be started at the
303   * appropriate time.
304   *
305   * @param  task        The task to be scheduled.
306   * @param  writeState  Indicates whether the current state information for
307   *                     the scheduler should be persisted to disk once the
308   *                     task is scheduled.
309   *
310   * @throws  DirectoryException  If a problem occurs while trying to schedule
311   *                              the task (e.g., there's already another task
312   *                              defined with the same ID).
313   */
314  public void scheduleTask(Task task, boolean writeState)
315         throws DirectoryException
316  {
317    schedulerLock.lock();
318
319
320    try
321    {
322      String id = task.getTaskID();
323
324      if (tasks.containsKey(id))
325      {
326        LocalizableMessage message = ERR_TASKSCHED_DUPLICATE_TASK_ID.get(id);
327        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message);
328      }
329
330      for (String dependencyID : task.getDependencyIDs())
331      {
332        Task t = tasks.get(dependencyID);
333        if (t == null)
334        {
335          LocalizableMessage message = ERR_TASKSCHED_DEPENDENCY_MISSING.get(id, dependencyID);
336          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
337        }
338      }
339
340      tasks.put(id, task);
341
342      TaskState state = shouldStart(task);
343      task.setTaskState(state);
344
345      if (state == TaskState.RUNNING)
346      {
347        TaskThread taskThread;
348        if (idleThreads.isEmpty())
349        {
350          taskThread = new TaskThread(this, nextThreadID++);
351          taskThread.start();
352        }
353        else
354        {
355          taskThread = idleThreads.removeFirst();
356        }
357
358        runningTasks.add(task);
359        activeThreads.put(task.getTaskID(), taskThread);
360        taskThread.setTask(task);
361      }
362      else if (TaskState.isDone(state))
363      {
364        if (state == TaskState.CANCELED_BEFORE_STARTING && task.isRecurring())
365        {
366          pendingTasks.add(task);
367        }
368        else
369        {
370          completedTasks.add(task);
371        }
372      }
373      else
374      {
375        pendingTasks.add(task);
376      }
377
378      if (writeState)
379      {
380        writeState();
381      }
382    }
383    finally
384    {
385      schedulerLock.unlock();
386    }
387  }
388
389
390
391  /**
392   * Attempts to cancel the task with the given task ID.  This will only cancel
393   * the task if it has not yet started running.  If it has started, then it
394   * will not be interrupted.
395   *
396   * @param  taskID  The task ID of the task to cancel.
397   *
398   * @return  The requested task, which may or may not have actually been
399   *          cancelled (the task state should make it possible to determine
400   *          whether it was cancelled), or <CODE>null</CODE> if there is no
401   *          such task.
402   */
403  public Task cancelTask(String taskID)
404  {
405    schedulerLock.lock();
406
407    try
408    {
409      Task t = tasks.get(taskID);
410      if (t == null)
411      {
412        return null;
413      }
414
415      if (TaskState.isPending(t.getTaskState()))
416      {
417        pendingTasks.remove(t);
418        t.setTaskState(TaskState.CANCELED_BEFORE_STARTING);
419        addCompletedTask(t);
420        writeState();
421      }
422
423      return t;
424    }
425    finally
426    {
427      schedulerLock.unlock();
428    }
429  }
430
431
432
433  /**
434   * Removes the specified pending task.  It will be completely removed rather
435   * than moving it to the set of completed tasks.
436   *
437   * @param  taskID  The task ID of the pending task to remove.
438   *
439   * @return  The task that was removed.
440   *
441   * @throws  DirectoryException  If the requested task is not in the pending
442   *                              queue.
443   */
444  public Task removePendingTask(String taskID)
445         throws DirectoryException
446  {
447    schedulerLock.lock();
448
449    try
450    {
451      Task t = tasks.get(taskID);
452      if (t == null)
453      {
454        LocalizableMessage message = ERR_TASKSCHED_REMOVE_PENDING_NO_SUCH_TASK.get(taskID);
455        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
456      }
457
458      if (TaskState.isPending(t.getTaskState()))
459      {
460        tasks.remove(taskID);
461        pendingTasks.remove(t);
462        writeState();
463        return t;
464      }
465      else
466      {
467        LocalizableMessage message = ERR_TASKSCHED_REMOVE_PENDING_NOT_PENDING.get(taskID);
468        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
469      }
470    }
471    finally
472    {
473      schedulerLock.unlock();
474    }
475  }
476
477
478
479  /**
480   * Removes the specified completed task.
481   *
482   * @param  taskID  The task ID of the completed task to remove.
483   *
484   * @return  The task that was removed.
485   *
486   * @throws  DirectoryException  If the requested task could not be found.
487   */
488  public Task removeCompletedTask(String taskID)
489         throws DirectoryException
490  {
491    schedulerLock.lock();
492
493    try
494    {
495      Iterator<Task> iterator = completedTasks.iterator();
496      while (iterator.hasNext())
497      {
498        Task t = iterator.next();
499        if (t.getTaskID().equals(taskID))
500        {
501          iterator.remove();
502          tasks.remove(taskID);
503          writeState();
504          return t;
505        }
506      }
507
508      LocalizableMessage message = ERR_TASKSCHED_REMOVE_COMPLETED_NO_SUCH_TASK.get(taskID);
509      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
510    }
511    finally
512    {
513      schedulerLock.unlock();
514    }
515  }
516
517
518
519  /**
520   * Indicates that processing has completed on the provided task thread and
521   * that it is now available for processing other tasks.  The thread may be
522   * immediately used for processing another task if appropriate.
523   *
524   * @param  taskThread     The thread that has completed processing on its
525   *                        previously-assigned task.
526   * @param  completedTask  The task for which processing has been completed.
527   * @param  taskState      The task state for this completed task.
528   *
529   * @return  <CODE>true</CODE> if the thread should continue running and
530   *          wait for the next task to process, or <CODE>false</CODE> if it
531   *          should exit immediately.
532   */
533  public boolean threadDone(TaskThread taskThread, Task completedTask,
534    TaskState taskState)
535  {
536    schedulerLock.lock();
537
538    try
539    {
540      completedTask.setCompletionTime(TimeThread.getTime());
541      completedTask.setTaskState(taskState);
542      addCompletedTask(completedTask);
543
544      try
545      {
546        completedTask.sendNotificationEMailMessage();
547      }
548      catch (Exception e)
549      {
550        logger.traceException(e);
551      }
552
553      String taskID = completedTask.getTaskID();
554      if (activeThreads.remove(taskID) == null)
555      {
556        return false;
557      }
558
559      // See if the task is part of a recurring task.
560      // If so, then schedule the next iteration.
561      scheduleNextRecurringTaskIteration(completedTask,
562              new GregorianCalendar());
563      writeState();
564
565      if (isRunning)
566      {
567        idleThreads.add(taskThread);
568        return true;
569      }
570      else
571      {
572        return false;
573      }
574    }
575    finally
576    {
577      schedulerLock.unlock();
578    }
579  }
580
581
582
583  /**
584   * Check if a given task is a recurring task iteration and re-schedule it.
585   * @param  completedTask  The task for which processing has been completed.
586   * @param  calendar  The calendar date and time to schedule from.
587   */
588  protected void scheduleNextRecurringTaskIteration(Task completedTask,
589          GregorianCalendar calendar)
590  {
591    String recurringTaskID = completedTask.getRecurringTaskID();
592    if (recurringTaskID != null)
593    {
594      RecurringTask recurringTask = recurringTasks.get(recurringTaskID);
595      if (recurringTask != null)
596      {
597        Task newIteration = null;
598        try {
599          newIteration = recurringTask.scheduleNextIteration(calendar);
600        } catch (DirectoryException de) {
601          logger.error(de.getMessageObject());
602        }
603        if (newIteration != null)
604        {
605          try
606          {
607            // If there is an existing task with the same id
608            // and it is in completed state, take its place.
609            Task t = tasks.get(newIteration.getTaskID());
610            if (t != null && TaskState.isDone(t.getTaskState()))
611            {
612              removeCompletedTask(t.getTaskID());
613            }
614
615            scheduleTask(newIteration, false);
616          }
617          catch (DirectoryException de)
618          {
619            // This task might have been already scheduled from before
620            // and thus got initialized from backing file, otherwise
621            // log error and continue.
622            if (de.getResultCode() != ResultCode.ENTRY_ALREADY_EXISTS)
623            {
624              logger.traceException(de);
625
626              LocalizableMessage message =
627                  ERR_TASKSCHED_ERROR_SCHEDULING_RECURRING_ITERATION.get(
628                      recurringTaskID, de.getMessageObject());
629              logger.error(message);
630
631              DirectoryServer.sendAlertNotification(this,
632                   ALERT_TYPE_CANNOT_SCHEDULE_RECURRING_ITERATION,
633                      message);
634            }
635          }
636        }
637      }
638    }
639  }
640
641
642
643  /**
644   * Adds the provided task to the set of completed tasks associated with the
645   * scheduler.  It will be automatically removed after the appropriate
646   * retention time has elapsed.
647   *
648   * @param  completedTask  The task for which processing has completed.
649   */
650  public void addCompletedTask(Task completedTask)
651  {
652    // The scheduler lock is reentrant, so even if we already hold it, we can
653    // acquire it again.
654    schedulerLock.lock();
655
656    try
657    {
658      completedTasks.add(completedTask);
659      runningTasks.remove(completedTask);
660
661      // If the task never ran set its completion
662      // time here explicitly so that it can be
663      // correctly evaluated for retention later.
664      if (completedTask.getCompletionTime() == -1)
665      {
666        completedTask.setCompletionTime(TimeThread.getTime());
667      }
668    }
669    finally
670    {
671      schedulerLock.unlock();
672    }
673  }
674
675
676
677  /**
678   * Stops the scheduler so that it will not start any scheduled tasks.  It will
679   * not attempt to interrupt any tasks that are already running.  Note that
680   * once the scheduler has been stopped, it cannot be restarted and it will be
681   * necessary to restart the task backend to start a new scheduler instance.
682   */
683  public void stopScheduler()
684  {
685    stopRequested = true;
686
687    try
688    {
689      schedulerThread.interrupt();
690    }
691    catch (Exception e)
692    {
693      logger.traceException(e);
694    }
695
696    try
697    {
698      schedulerThread.join();
699    }
700    catch (Exception e)
701    {
702      logger.traceException(e);
703    }
704
705    pendingTasks.clear();
706    runningTasks.clear();
707    completedTasks.clear();
708    tasks.clear();
709
710    for (TaskThread thread : idleThreads)
711    {
712      LocalizableMessage message = INFO_TASKBE_INTERRUPTED_BY_SHUTDOWN.get();
713      thread.interruptTask(TaskState.STOPPED_BY_SHUTDOWN, message, true);
714    }
715  }
716
717
718
719  /**
720   * Attempts to interrupt any tasks that are actively running.  This will not
721   * make any attempt to stop the scheduler.
722   *
723   * @param  interruptState   The state that should be assigned to the tasks if
724   *                          they are successfully interrupted.
725   * @param  interruptReason  A message indicating the reason that the tasks
726   *                          are to be interrupted.
727   * @param  waitForStop      Indicates whether this method should wait until
728   *                          all active tasks have stopped before returning.
729   */
730  public void interruptRunningTasks(TaskState interruptState,
731                                    LocalizableMessage interruptReason,
732                                    boolean waitForStop)
733  {
734    // Grab a copy of the running threads so that we can operate on them without
735    // holding the lock.
736    LinkedList<TaskThread> threadList = new LinkedList<>();
737
738    schedulerLock.lock();
739    try
740    {
741      threadList.addAll(activeThreads.values());
742    }
743    finally
744    {
745      schedulerLock.unlock();
746    }
747
748
749    // Iterate through all the task threads and request that they stop
750    // processing.
751    for (TaskThread t : threadList)
752    {
753      try
754      {
755        t.interruptTask(interruptState, interruptReason, true);
756      }
757      catch (Exception e)
758      {
759        logger.traceException(e);
760      }
761    }
762
763
764    // If we should actually wait for all the task threads to stop, then do so.
765    if (waitForStop)
766    {
767      for (TaskThread t : threadList)
768      {
769        try
770        {
771          t.join();
772        }
773        catch (Exception e)
774        {
775          logger.traceException(e);
776        }
777      }
778    }
779  }
780
781
782
783  /**
784   * Operates in a loop, launching tasks at the appropriate time and performing
785   * any necessary periodic cleanup.
786   */
787  @Override
788  public void run()
789  {
790    isRunning       = true;
791    schedulerThread = currentThread();
792
793    try
794    {
795      while (! stopRequested)
796      {
797        schedulerLock.lock();
798
799        boolean writeState = false;
800        long sleepTime = MAX_SLEEP_TIME;
801
802        try
803        {
804          // If there are any pending tasks that need to be started, then do so
805          // now.
806          Iterator<Task> iterator = pendingTasks.iterator();
807          while (iterator.hasNext())
808          {
809            Task t = iterator.next();
810            TaskState state = shouldStart(t);
811
812            if (state == TaskState.RUNNING)
813            {
814              TaskThread taskThread;
815              if (idleThreads.isEmpty())
816              {
817                taskThread = new TaskThread(this, nextThreadID++);
818                taskThread.start();
819              }
820              else
821              {
822                taskThread = idleThreads.removeFirst();
823              }
824
825              runningTasks.add(t);
826              activeThreads.put(t.getTaskID(), taskThread);
827              taskThread.setTask(t);
828
829              iterator.remove();
830              writeState = true;
831            }
832            else if (state == TaskState.WAITING_ON_START_TIME)
833            {
834              // If we're waiting for the start time to arrive, then see if that
835              // will come before the next sleep time is up.
836              long waitTime = t.getScheduledStartTime() - TimeThread.getTime();
837              sleepTime = Math.min(sleepTime, waitTime);
838            }
839            // Recurring task iteration has to spawn the next one
840            // even if the current iteration has been canceled.
841            else if (state == TaskState.CANCELED_BEFORE_STARTING && t.isRecurring())
842            {
843              if (t.getScheduledStartTime() > TimeThread.getTime()) {
844                // If we're waiting for the start time to arrive,
845                // then see if that will come before the next sleep time is up.
846                long waitTime =
847                  t.getScheduledStartTime() - TimeThread.getTime();
848                sleepTime = Math.min(sleepTime, waitTime);
849              } else {
850                TaskThread taskThread;
851                if (idleThreads.isEmpty()) {
852                  taskThread = new TaskThread(this, nextThreadID++);
853                  taskThread.start();
854                } else {
855                  taskThread = idleThreads.removeFirst();
856                }
857                runningTasks.add(t);
858                activeThreads.put(t.getTaskID(), taskThread);
859                taskThread.setTask(t);
860              }
861            }
862
863            if (state != t.getTaskState())
864            {
865              t.setTaskState(state);
866              writeState = true;
867            }
868          }
869
870
871          // Clean up any completed tasks that have been around long enough.
872          long retentionTimeMillis =
873            TimeUnit.SECONDS.toMillis(taskBackend.getRetentionTime());
874          long oldestRetainedCompletionTime =
875                    TimeThread.getTime() - retentionTimeMillis;
876          iterator = completedTasks.iterator();
877          while (iterator.hasNext())
878          {
879            Task t = iterator.next();
880            if (t.getCompletionTime() < oldestRetainedCompletionTime)
881            {
882              iterator.remove();
883              tasks.remove(t.getTaskID());
884              writeState = true;
885            }
886          }
887
888          // If anything changed, then make sure that the on-disk state gets
889          // updated.
890          if (writeState)
891          {
892            writeState();
893          }
894        }
895        finally
896        {
897          schedulerLock.unlock();
898        }
899
900
901        try
902        {
903          if (sleepTime > 0)
904          {
905            Thread.sleep(sleepTime);
906          }
907        } catch (InterruptedException ie) {}
908      }
909    }
910    finally
911    {
912      isRunning = false;
913    }
914  }
915
916
917
918  /**
919   * Determines whether the specified task should start running.  This is based
920   * on the start time, the set of dependencies, and whether or not the
921   * scheduler is active.  Note that the caller to this method must hold the
922   * scheduler lock.
923   *
924   * @param  task  The task for which to make the determination.
925   *
926   * @return  The task state that should be used for the task.  It should be
927   *          RUNNING if the task should be started, or some other state if not.
928   */
929  private TaskState shouldStart(Task task)
930  {
931    // If the task has finished we don't want to restart it
932    TaskState state = task.getTaskState();
933
934    // Reset task state if recurring.
935    if (state == TaskState.RECURRING) {
936      state = null;
937    }
938
939    if (state != null && TaskState.isDone(state))
940    {
941      return state;
942    }
943
944    if (! isRunning)
945    {
946      return TaskState.UNSCHEDULED;
947    }
948
949    if (task.getScheduledStartTime() > TimeThread.getTime())
950    {
951      return TaskState.WAITING_ON_START_TIME;
952    }
953
954    LinkedList<String> dependencyIDs = task.getDependencyIDs();
955    if (dependencyIDs != null)
956    {
957      for (String dependencyID : dependencyIDs)
958      {
959        Task t = tasks.get(dependencyID);
960        if (t != null)
961        {
962          TaskState tState = t.getTaskState();
963          if (!TaskState.isDone(tState))
964          {
965            return TaskState.WAITING_ON_DEPENDENCY;
966          }
967          if (!TaskState.isSuccessful(tState))
968          {
969            FailedDependencyAction action = task.getFailedDependencyAction();
970            switch (action)
971            {
972              case CANCEL:
973                cancelTask(task.getTaskID());
974                return task.getTaskState();
975              case DISABLE:
976                task.setTaskState(TaskState.DISABLED);
977                return task.getTaskState();
978              default:
979                break;
980            }
981          }
982        }
983      }
984    }
985
986    return TaskState.RUNNING;
987  }
988
989
990
991  /**
992   * Populates the scheduler with information read from the task backing file.
993   * If no backing file is found, then create a new one.  The caller must
994   * already hold the scheduler lock or otherwise ensure that this is a
995   * threadsafe operation.
996   *
997   * @throws  InitializationException  If a fatal error occurs while attempting
998   *                                   to perform the initialization.
999   */
1000  private void initializeTasksFromBackingFile()
1001          throws InitializationException
1002  {
1003    String backingFilePath = taskBackend.getTaskBackingFile();
1004
1005    try
1006    {
1007      File backingFile = getFileForPath(backingFilePath);
1008      if (! backingFile.exists())
1009      {
1010        createNewTaskBackingFile();
1011        return;
1012      }
1013
1014
1015      LDIFImportConfig importConfig = new LDIFImportConfig(backingFilePath);
1016      LDIFReader ldifReader = new LDIFReader(importConfig);
1017
1018      taskRootEntry            = null;
1019      recurringTaskParentEntry = null;
1020      scheduledTaskParentEntry = null;
1021
1022      while (true)
1023      {
1024        Entry entry;
1025
1026        try
1027        {
1028          entry = ldifReader.readEntry();
1029        }
1030        catch (LDIFException le)
1031        {
1032          logger.traceException(le);
1033
1034          if (le.canContinueReading())
1035          {
1036            logger.error(ERR_TASKSCHED_CANNOT_PARSE_ENTRY_RECOVERABLE,
1037                backingFilePath, le.getLineNumber(), le.getMessage());
1038
1039            continue;
1040          }
1041          else
1042          {
1043            try
1044            {
1045              ldifReader.close();
1046            }
1047            catch (Exception e)
1048            {
1049              logger.traceException(e);
1050            }
1051
1052            LocalizableMessage message = ERR_TASKSCHED_CANNOT_PARSE_ENTRY_FATAL.get(
1053                backingFilePath, le.getLineNumber(), le.getMessage());
1054            throw new InitializationException(message);
1055          }
1056        }
1057
1058        if (entry == null)
1059        {
1060          break;
1061        }
1062
1063        DN entryDN = entry.getName();
1064        if (entryDN.equals(taskBackend.getTaskRootDN()))
1065        {
1066          taskRootEntry = entry;
1067        }
1068        else if (entryDN.equals(taskBackend.getRecurringTasksParentDN()))
1069        {
1070          recurringTaskParentEntry = entry;
1071        }
1072        else if (entryDN.equals(taskBackend.getScheduledTasksParentDN()))
1073        {
1074          scheduledTaskParentEntry = entry;
1075        }
1076        else
1077        {
1078          DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN);
1079          if (parentDN == null)
1080          {
1081            logger.error(ERR_TASKSCHED_ENTRY_HAS_NO_PARENT, entryDN, taskBackend.getTaskRootDN());
1082          }
1083          else if (parentDN.equals(taskBackend.getScheduledTasksParentDN()))
1084          {
1085            try
1086            {
1087              Task task = entryToScheduledTask(entry, null);
1088              if (TaskState.isDone(task.getTaskState()))
1089              {
1090                String id = task.getTaskID();
1091                if (tasks.containsKey(id))
1092                {
1093                  logger.warn(WARN_TASKSCHED_DUPLICATE_TASK_ID, id);
1094                }
1095                else
1096                {
1097                  completedTasks.add(task);
1098                  tasks.put(id, task);
1099                }
1100              }
1101              else
1102              {
1103                scheduleTask(task, false);
1104              }
1105            }
1106            catch (DirectoryException de)
1107            {
1108              logger.traceException(de);
1109              logger.error(ERR_TASKSCHED_CANNOT_SCHEDULE_TASK_FROM_ENTRY, entryDN, de.getMessageObject());
1110            }
1111          }
1112          else if (parentDN.equals(taskBackend.getRecurringTasksParentDN()))
1113          {
1114            try
1115            {
1116              RecurringTask recurringTask = entryToRecurringTask(entry);
1117              addRecurringTask(recurringTask, false);
1118            }
1119            catch (DirectoryException de)
1120            {
1121              logger.traceException(de);
1122              logger.error(ERR_TASKSCHED_CANNOT_SCHEDULE_RECURRING_TASK_FROM_ENTRY, entryDN, de.getMessageObject());
1123            }
1124          }
1125          else
1126          {
1127            logger.error(ERR_TASKSCHED_INVALID_TASK_ENTRY_DN, entryDN, backingFilePath);
1128          }
1129        }
1130      }
1131
1132      ldifReader.close();
1133    }
1134    catch (IOException ioe)
1135    {
1136      logger.traceException(ioe);
1137
1138      LocalizableMessage message = ERR_TASKSCHED_ERROR_READING_TASK_BACKING_FILE.get(
1139          backingFilePath, stackTraceToSingleLineString(ioe));
1140      throw new InitializationException(message, ioe);
1141    }
1142  }
1143
1144
1145
1146  /**
1147   * Creates a new task backing file that contains only the basic structure but
1148   * no scheduled or recurring task entries.  The caller must already hold the
1149   * scheduler lock or otherwise ensure that this is a threadsafe operation.
1150   *
1151   * @throws  InitializationException  If a problem occurs while attempting to
1152   *                                   create the backing file.
1153   */
1154  private void createNewTaskBackingFile()
1155          throws InitializationException
1156  {
1157    String backingFile = taskBackend.getTaskBackingFile();
1158    LDIFExportConfig exportConfig =
1159         new LDIFExportConfig(backingFile, ExistingFileBehavior.OVERWRITE);
1160
1161    try
1162    {
1163      LDIFWriter writer = new LDIFWriter(exportConfig);
1164
1165      // First, write a header to the top of the file to indicate that it should
1166      // not be manually edited.
1167      writer.writeComment(INFO_TASKBE_BACKING_FILE_HEADER.get(), 80);
1168
1169
1170      // Next, create the required hierarchical entries and add them to the
1171      // LDIF.
1172      taskRootEntry = createEntry(taskBackend.getTaskRootDN());
1173      writer.writeEntry(taskRootEntry);
1174
1175      scheduledTaskParentEntry =
1176           createEntry(taskBackend.getScheduledTasksParentDN());
1177      writer.writeEntry(scheduledTaskParentEntry);
1178
1179      recurringTaskParentEntry =
1180           createEntry(taskBackend.getRecurringTasksParentDN());
1181      writer.writeEntry(recurringTaskParentEntry);
1182
1183
1184      // Close the file and we're done.
1185      writer.close();
1186    }
1187    catch (IOException ioe)
1188    {
1189      logger.traceException(ioe);
1190
1191      LocalizableMessage message = ERR_TASKSCHED_CANNOT_CREATE_BACKING_FILE.get(
1192          backingFile, stackTraceToSingleLineString(ioe));
1193      throw new InitializationException(message, ioe);
1194    }
1195    catch (LDIFException le)
1196    {
1197      logger.traceException(le);
1198      LocalizableMessage message = ERR_TASKSCHED_CANNOT_CREATE_BACKING_FILE.get(
1199          backingFile, le.getMessage());
1200      throw new InitializationException(message, le);
1201    }
1202  }
1203
1204
1205
1206  /**
1207   * Writes state information about all tasks and recurring tasks to disk.
1208   */
1209  public void writeState()
1210  {
1211    String backingFilePath = taskBackend.getTaskBackingFile();
1212    String tmpFilePath     = backingFilePath + ".tmp";
1213    LDIFExportConfig exportConfig =
1214         new LDIFExportConfig(tmpFilePath, ExistingFileBehavior.OVERWRITE);
1215
1216
1217    schedulerLock.lock();
1218
1219    try
1220    {
1221      LDIFWriter writer = new LDIFWriter(exportConfig);
1222
1223      // First, write a header to the top of the file to indicate that it should
1224      // not be manually edited.
1225      writer.writeComment(INFO_TASKBE_BACKING_FILE_HEADER.get(), 80);
1226
1227
1228      // Next, write the structural entries to the top of the LDIF.
1229      writer.writeEntry(taskRootEntry);
1230      writer.writeEntry(scheduledTaskParentEntry);
1231      writer.writeEntry(recurringTaskParentEntry);
1232
1233
1234      // Iterate through all the recurring tasks and write them to LDIF.
1235      for (RecurringTask recurringTask : recurringTasks.values())
1236      {
1237        writer.writeEntry(recurringTask.getRecurringTaskEntry());
1238      }
1239
1240
1241      // Iterate through all the scheduled tasks and write them to LDIF.
1242      for (Task task : tasks.values())
1243      {
1244        writer.writeEntry(task.getTaskEntry());
1245      }
1246
1247
1248      // Close the file.
1249      writer.close();
1250
1251
1252      // See if there is a ".save" file.  If so, then delete it.
1253      File saveFile = getFileForPath(backingFilePath + ".save");
1254      try
1255      {
1256        if (saveFile.exists())
1257        {
1258          saveFile.delete();
1259        }
1260      }
1261      catch (Exception e)
1262      {
1263        logger.traceException(e);
1264      }
1265
1266
1267      // If there is an existing backing file, then rename it to ".save".
1268      File backingFile = getFileForPath(backingFilePath);
1269      try
1270      {
1271        if (backingFile.exists())
1272        {
1273          backingFile.renameTo(saveFile);
1274        }
1275      }
1276      catch (Exception e)
1277      {
1278        logger.traceException(e);
1279
1280        LocalizableMessage message = WARN_TASKSCHED_CANNOT_RENAME_CURRENT_BACKING_FILE.get(
1281            backingFilePath, saveFile.getAbsolutePath(), stackTraceToSingleLineString(e));
1282        logger.warn(message);
1283        DirectoryServer.sendAlertNotification(
1284            this, ALERT_TYPE_CANNOT_RENAME_CURRENT_TASK_FILE, message);
1285      }
1286
1287
1288      // Rename the ".tmp" file into place.
1289      File tmpFile = getFileForPath(tmpFilePath);
1290      try
1291      {
1292        tmpFile.renameTo(backingFile);
1293      }
1294      catch (Exception e)
1295      {
1296        logger.traceException(e);
1297
1298        LocalizableMessage message = ERR_TASKSCHED_CANNOT_RENAME_NEW_BACKING_FILE.get(
1299            tmpFilePath, backingFilePath, stackTraceToSingleLineString(e));
1300        logger.error(message);
1301        DirectoryServer.sendAlertNotification(
1302            this, ALERT_TYPE_CANNOT_RENAME_NEW_TASK_FILE, message);
1303      }
1304    }
1305    catch (LDIFException le)
1306    {
1307      logger.traceException(le);
1308      LocalizableMessage message =
1309          ERR_TASKSCHED_CANNOT_WRITE_BACKING_FILE.get(tmpFilePath, le
1310              .getMessage());
1311      logger.error(message);
1312      DirectoryServer.sendAlertNotification(this,
1313                           ALERT_TYPE_CANNOT_WRITE_TASK_FILE, message);
1314    }
1315    catch (Exception e)
1316    {
1317      logger.traceException(e);
1318      LocalizableMessage message =
1319          ERR_TASKSCHED_CANNOT_WRITE_BACKING_FILE.get(tmpFilePath,
1320              stackTraceToSingleLineString(e));
1321      logger.error(message);
1322      DirectoryServer.sendAlertNotification(this,
1323                           ALERT_TYPE_CANNOT_WRITE_TASK_FILE, message);
1324    }
1325    finally
1326    {
1327      schedulerLock.unlock();
1328    }
1329  }
1330
1331
1332
1333  /**
1334   * Retrieves the total number of entries in the task backend.
1335   *
1336   * @return  The total number of entries in the task backend.
1337   */
1338  public long getEntryCount()
1339  {
1340    schedulerLock.lock();
1341
1342    try
1343    {
1344      return tasks.size() + recurringTasks.size() + 3;
1345    }
1346    finally
1347    {
1348      schedulerLock.unlock();
1349    }
1350  }
1351
1352  /**
1353   * Retrieves the number of scheduled tasks in the task backend.
1354   *
1355   * @return  The total number of entries in the task backend.
1356   */
1357  public long getScheduledTaskCount()
1358  {
1359    schedulerLock.lock();
1360
1361    try
1362    {
1363      return tasks.size();
1364    }
1365    finally
1366    {
1367      schedulerLock.unlock();
1368    }
1369  }
1370
1371
1372
1373  /**
1374   * Retrieves the number of recurring tasks in the task backend.
1375   *
1376   * @return  The total number of entries in the task backend.
1377   */
1378  public long getRecurringTaskCount()
1379  {
1380    schedulerLock.lock();
1381
1382    try
1383    {
1384      return recurringTasks.size();
1385    }
1386    finally
1387    {
1388      schedulerLock.unlock();
1389    }
1390  }
1391
1392
1393
1394  /**
1395   * Retrieves the task backend with which this scheduler is associated.
1396   *
1397   * @return  The task backend with which this scheduler is associated.
1398   */
1399  public TaskBackend getTaskBackend()
1400  {
1401    return taskBackend;
1402  }
1403
1404
1405
1406  /**
1407   * Retrieves the root entry that is the common ancestor for all entries in the
1408   * task backend.
1409   *
1410   * @return  The root entry that is the common ancestor for all entries in the
1411   *          task backend.
1412   */
1413  public Entry getTaskRootEntry()
1414  {
1415    return taskRootEntry.duplicate(true);
1416  }
1417
1418
1419
1420  /**
1421   * Retrieves the entry that is the immediate parent for all scheduled task
1422   * entries in the task backend.
1423   *
1424   * @return  The entry that is the immediate parent for all scheduled task
1425   *          entries in the task backend.
1426   */
1427  public Entry getScheduledTaskParentEntry()
1428  {
1429    return scheduledTaskParentEntry.duplicate(true);
1430  }
1431
1432
1433
1434  /**
1435   * Retrieves the entry that is the immediate parent for all recurring task
1436   * entries in the task backend.
1437   *
1438   * @return  The entry that is the immediate parent for all recurring task
1439   *          entries in the task backend.
1440   */
1441  public Entry getRecurringTaskParentEntry()
1442  {
1443    return recurringTaskParentEntry.duplicate(true);
1444  }
1445
1446
1447
1448  /**
1449   * Retrieves the scheduled task with the given task ID.
1450   *
1451   * @param  taskID  The task ID for the scheduled task to retrieve.
1452   *
1453   * @return  The requested scheduled task, or <CODE>null</CODE> if there is no
1454   *          such task.
1455   */
1456  public Task getScheduledTask(String taskID)
1457  {
1458    schedulerLock.lock();
1459
1460    try
1461    {
1462      return tasks.get(taskID);
1463    }
1464    finally
1465    {
1466      schedulerLock.unlock();
1467    }
1468  }
1469
1470
1471
1472  /**
1473   * Retrieves the scheduled task created from the specified entry.
1474   *
1475   * @param  taskEntryDN  The DN of the task configuration entry associated
1476   *                       with the task to retrieve.
1477   *
1478   * @return  The requested scheduled task, or <CODE>null</CODE> if there is no
1479   *          such task.
1480   */
1481  public Task getScheduledTask(DN taskEntryDN)
1482  {
1483    schedulerLock.lock();
1484
1485    try
1486    {
1487      for (Task t : tasks.values())
1488      {
1489        if (taskEntryDN.equals(t.getTaskEntry().getName()))
1490        {
1491          return t;
1492        }
1493      }
1494
1495      return null;
1496    }
1497    finally
1498    {
1499      schedulerLock.unlock();
1500    }
1501  }
1502
1503
1504
1505  /**
1506   * Indicates whether the current thread already holds a lock on the scheduler.
1507   *
1508   * @return  {@code true} if the current thread holds the scheduler lock, or
1509   *          {@code false} if not.
1510   */
1511  boolean holdsSchedulerLock()
1512  {
1513    return schedulerLock.isHeldByCurrentThread();
1514  }
1515
1516
1517
1518  /**
1519   * Attempts to acquire a write lock on the specified entry, trying as many
1520   * times as necessary until the lock has been acquired.
1521   *
1522   * @param  entryDN  The DN of the entry for which to acquire the write lock.
1523   *
1524   * @return  The write lock that has been acquired for the entry.
1525   */
1526  DNLock writeLockEntry(DN entryDN)
1527  {
1528    DNLock lock = null;
1529    while (lock == null)
1530    {
1531      lock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN);
1532    }
1533    return lock;
1534  }
1535
1536
1537
1538  /**
1539   * Attempts to acquire a read lock on the specified entry.
1540   *
1541   * @param  entryDN  The DN of the entry for which to acquire the read lock.
1542   *
1543   * @return  The read lock that has been acquired for the entry.
1544   *
1545   * @throws  DirectoryException  If the read lock cannot be acquired.
1546   */
1547  DNLock readLockEntry(DN entryDN) throws DirectoryException
1548  {
1549    final DNLock lock = DirectoryServer.getLockManager().tryReadLockEntry(entryDN);
1550    if (lock != null)
1551    {
1552      return lock;
1553    }
1554    throw new DirectoryException(ResultCode.BUSY, ERR_BACKEND_CANNOT_LOCK_ENTRY.get(entryDN));
1555  }
1556
1557
1558
1559  /**
1560   * Retrieves the scheduled task entry with the provided DN.  The caller should
1561   * hold a read lock on the target entry.
1562   *
1563   * @param  scheduledTaskEntryDN  The entry DN that indicates which scheduled
1564   *                               task entry to retrieve.
1565   *
1566   * @return  The scheduled task entry with the provided DN, or
1567   *          <CODE>null</CODE> if no scheduled task has the provided DN.
1568   */
1569  public Entry getScheduledTaskEntry(DN scheduledTaskEntryDN)
1570  {
1571    schedulerLock.lock();
1572
1573    try
1574    {
1575      for (Task task : tasks.values())
1576      {
1577        Entry taskEntry = task.getTaskEntry();
1578
1579        if (scheduledTaskEntryDN.equals(taskEntry.getName()))
1580        {
1581          return taskEntry.duplicate(true);
1582        }
1583      }
1584
1585      return null;
1586    }
1587    finally
1588    {
1589      schedulerLock.unlock();
1590    }
1591  }
1592
1593
1594
1595  /**
1596   * Compares the filter in the provided search operation against each of the
1597   * task entries, returning any that match.  Note that only the search filter
1598   * will be used -- the base and scope will be ignored, so the caller must
1599   * ensure that they are correct for scheduled tasks.
1600   *
1601   * @param  searchOperation  The search operation to use when performing the
1602   *                          search.
1603   *
1604   * @return  <CODE>true</CODE> if processing should continue on the search
1605   *          operation, or <CODE>false</CODE> if it should not for some reason
1606   *          (e.g., a size or time limit was reached).
1607   *
1608   * @throws  DirectoryException  If a problem occurs while processing the
1609   *                              search operation against the scheduled tasks.
1610   */
1611  public boolean searchScheduledTasks(SearchOperation searchOperation)
1612         throws DirectoryException
1613  {
1614    SearchFilter filter = searchOperation.getFilter();
1615
1616    schedulerLock.lock();
1617
1618    try
1619    {
1620      for (Task t : tasks.values())
1621      {
1622        DN taskEntryDN = t.getTaskEntryDN();
1623        DNLock lock = readLockEntry(taskEntryDN);
1624        try
1625        {
1626          Entry e = t.getTaskEntry().duplicate(true);
1627          if (filter.matchesEntry(e) && !searchOperation.returnEntry(e, null))
1628          {
1629            return false;
1630          }
1631        }
1632        finally
1633        {
1634          lock.unlock();
1635        }
1636      }
1637
1638      return true;
1639    }
1640    finally
1641    {
1642      schedulerLock.unlock();
1643    }
1644  }
1645
1646
1647
1648  /**
1649   * Retrieves the recurring task with the given recurring task ID.
1650   *
1651   * @param  recurringTaskID  The recurring task ID for the recurring task to
1652   *                          retrieve.
1653   *
1654   * @return  The requested recurring task, or <CODE>null</CODE> if there is no
1655   *          such recurring task.
1656   */
1657  public RecurringTask getRecurringTask(String recurringTaskID)
1658  {
1659    schedulerLock.lock();
1660
1661    try
1662    {
1663      return recurringTasks.get(recurringTaskID);
1664    }
1665    finally
1666    {
1667      schedulerLock.unlock();
1668    }
1669  }
1670
1671
1672
1673  /**
1674   * Retrieves the recurring task with the given recurring task ID.
1675   *
1676   * @param  recurringTaskEntryDN  The recurring task ID for the recurring task
1677   *                               to retrieve.
1678   *
1679   * @return  The requested recurring task, or <CODE>null</CODE> if there is no
1680   *          such recurring task.
1681   */
1682  public RecurringTask getRecurringTask(DN recurringTaskEntryDN)
1683  {
1684    schedulerLock.lock();
1685
1686    try
1687    {
1688      for (RecurringTask rt : recurringTasks.values())
1689      {
1690        if (recurringTaskEntryDN.equals(rt.getRecurringTaskEntry().getName()))
1691        {
1692          return rt;
1693        }
1694      }
1695
1696      return null;
1697    }
1698    finally
1699    {
1700      schedulerLock.unlock();
1701    }
1702  }
1703
1704
1705
1706  /**
1707   * Retrieves the recurring task entry with the provided DN.  The caller should
1708   * hold a read lock on the target entry.
1709   *
1710   * @param  recurringTaskEntryDN  The entry DN that indicates which recurring
1711   *                               task entry to retrieve.
1712   *
1713   * @return  The recurring task entry with the provided DN, or
1714   *          <CODE>null</CODE> if no recurring task has the provided DN.
1715   */
1716  public Entry getRecurringTaskEntry(DN recurringTaskEntryDN)
1717  {
1718    schedulerLock.lock();
1719
1720    try
1721    {
1722      for (RecurringTask recurringTask : recurringTasks.values())
1723      {
1724        Entry recurringTaskEntry = recurringTask.getRecurringTaskEntry();
1725
1726        if (recurringTaskEntryDN.equals(recurringTaskEntry.getName()))
1727        {
1728          return recurringTaskEntry.duplicate(true);
1729        }
1730      }
1731
1732      return null;
1733    }
1734    finally
1735    {
1736      schedulerLock.unlock();
1737    }
1738  }
1739
1740
1741
1742  /**
1743   * Compares the filter in the provided search operation against each of the
1744   * recurring task entries, returning any that match.  Note that only the
1745   * search filter will be used -- the base and scope will be ignored, so the
1746   * caller must ensure that they are correct for recurring tasks.
1747   *
1748   * @param  searchOperation  The search operation to use when performing the
1749   *                          search.
1750   *
1751   * @return  <CODE>true</CODE> if processing should continue on the search
1752   *          operation, or <CODE>false</CODE> if it should not for some reason
1753   *          (e.g., a size or time limit was reached).
1754   *
1755   * @throws  DirectoryException  If a problem occurs while processing the
1756   *                              search operation against the recurring tasks.
1757   */
1758  public boolean searchRecurringTasks(SearchOperation searchOperation)
1759         throws DirectoryException
1760  {
1761    SearchFilter filter = searchOperation.getFilter();
1762
1763    schedulerLock.lock();
1764
1765    try
1766    {
1767      for (RecurringTask rt : recurringTasks.values())
1768      {
1769        DN recurringTaskEntryDN = rt.getRecurringTaskEntryDN();
1770        DNLock lock = readLockEntry(recurringTaskEntryDN);
1771        try
1772        {
1773          Entry e = rt.getRecurringTaskEntry().duplicate(true);
1774          if (filter.matchesEntry(e) && ! searchOperation.returnEntry(e, null))
1775          {
1776            return false;
1777          }
1778        }
1779        finally
1780        {
1781          lock.unlock();
1782        }
1783      }
1784
1785      return true;
1786    }
1787    finally
1788    {
1789      schedulerLock.unlock();
1790    }
1791  }
1792
1793
1794
1795  /**
1796   * Decodes the contents of the provided entry as a scheduled task.  The
1797   * resulting task will not actually be scheduled for processing.
1798   *
1799   * @param  entry      The entry to decode as a scheduled task.
1800   * @param  operation  The operation used to create this task in the server, or
1801   *                    {@code null} if the operation is not available.
1802   *
1803   * @return  The scheduled task decoded from the provided entry.
1804   *
1805   * @throws  DirectoryException  If the provided entry cannot be decoded as a
1806   *                              scheduled task.
1807   */
1808  public Task entryToScheduledTask(Entry entry, Operation operation)
1809         throws DirectoryException
1810  {
1811    // Get the name of the class that implements the task logic.
1812    AttributeType attrType = DirectoryServer.getAttributeType(ATTR_TASK_CLASS);
1813    List<Attribute> attrList = entry.getAttribute(attrType);
1814    if (attrList.isEmpty())
1815    {
1816      LocalizableMessage message = ERR_TASKSCHED_NO_CLASS_ATTRIBUTE.get(ATTR_TASK_ID);
1817      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1818    }
1819
1820    if (attrList.size() > 1)
1821    {
1822      LocalizableMessage message = ERR_TASKSCHED_MULTIPLE_CLASS_TYPES.get(ATTR_TASK_ID);
1823      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1824    }
1825
1826    Attribute attr = attrList.get(0);
1827    if (attr.isEmpty())
1828    {
1829      LocalizableMessage message = ERR_TASKSCHED_NO_CLASS_VALUES.get(ATTR_TASK_ID);
1830      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1831    }
1832
1833    Iterator<ByteString> iterator = attr.iterator();
1834    ByteString value = iterator.next();
1835    if (iterator.hasNext())
1836    {
1837      LocalizableMessage message = ERR_TASKSCHED_MULTIPLE_CLASS_VALUES.get(ATTR_TASK_ID);
1838      throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, message);
1839    }
1840
1841    // Try to load the specified class.
1842    String taskClassName = value.toString();
1843    Class<?> taskClass;
1844    try
1845    {
1846      taskClass = DirectoryServer.loadClass(taskClassName);
1847    }
1848    catch (Exception e)
1849    {
1850      logger.traceException(e);
1851
1852      LocalizableMessage message = ERR_TASKSCHED_CANNOT_LOAD_CLASS.
1853          get(taskClassName, ATTR_TASK_CLASS, stackTraceToSingleLineString(e));
1854      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
1855    }
1856
1857    // Instantiate the class as a task.
1858    Task task;
1859    try
1860    {
1861      task = (Task) taskClass.newInstance();
1862    }
1863    catch (Exception e)
1864    {
1865      logger.traceException(e);
1866
1867      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INSTANTIATE_CLASS_AS_TASK.get(
1868          taskClassName, Task.class.getName());
1869      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
1870    }
1871
1872    // Perform the necessary internal and external initialization for the task.
1873    try
1874    {
1875      task.initializeTaskInternal(serverContext, this, entry);
1876    }
1877    catch (InitializationException ie)
1878    {
1879      logger.traceException(ie);
1880
1881      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INITIALIZE_INTERNAL.get(
1882          taskClassName, ie.getMessage());
1883      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
1884    }
1885    catch (Exception e)
1886    {
1887      LocalizableMessage message = ERR_TASKSCHED_CANNOT_INITIALIZE_INTERNAL.get(
1888          taskClassName, stackTraceToSingleLineString(e));
1889      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
1890    }
1891
1892    if (!TaskState.isDone(task.getTaskState()) &&
1893        !DirectoryServer.getAllowedTasks().contains(taskClassName))
1894    {
1895      LocalizableMessage message = ERR_TASKSCHED_NOT_ALLOWED_TASK.get(taskClassName);
1896      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
1897    }
1898
1899    task.setOperation(operation);
1900
1901    // Avoid task specific initialization for completed tasks.
1902    if (!TaskState.isDone(task.getTaskState())) {
1903      task.initializeTask();
1904    }
1905    task.setOperation(null);
1906
1907    return task;
1908  }
1909
1910
1911
1912  /**
1913   * Decodes the contents of the provided entry as a recurring task.  The
1914   * resulting recurring task will not actually be added to the scheduler.
1915   *
1916   * @param  entry  The entry to decode as a recurring task.
1917   *
1918   * @return  The recurring task decoded from the provided entry.
1919   *
1920   * @throws  DirectoryException  If the provided entry cannot be decoded as a
1921   *                              recurring task.
1922   */
1923  public RecurringTask entryToRecurringTask(Entry entry)
1924         throws DirectoryException
1925  {
1926    return new RecurringTask(serverContext, this, entry);
1927  }
1928
1929
1930
1931  /**
1932   * Retrieves the DN of the configuration entry with which this alert generator
1933   * is associated.
1934   *
1935   * @return  The DN of the configuration entry with which this alert generator
1936   *          is associated.
1937   */
1938  @Override
1939  public DN getComponentEntryDN()
1940  {
1941    return taskBackend.getConfigEntryDN();
1942  }
1943
1944
1945
1946  /**
1947   * Retrieves the fully-qualified name of the Java class for this alert
1948   * generator implementation.
1949   *
1950   * @return  The fully-qualified name of the Java class for this alert
1951   *          generator implementation.
1952   */
1953  @Override
1954  public String getClassName()
1955  {
1956    return CLASS_NAME;
1957  }
1958
1959
1960
1961  /**
1962   * Retrieves information about the set of alerts that this generator may
1963   * produce.  The map returned should be between the notification type for a
1964   * particular notification and the human-readable description for that
1965   * notification.  This alert generator must not generate any alerts with types
1966   * that are not contained in this list.
1967   *
1968   * @return  Information about the set of alerts that this generator may
1969   *          produce.
1970   */
1971  @Override
1972  public LinkedHashMap<String,String> getAlerts()
1973  {
1974    LinkedHashMap<String, String> alerts = new LinkedHashMap<>();
1975
1976    alerts.put(ALERT_TYPE_CANNOT_SCHEDULE_RECURRING_ITERATION,
1977               ALERT_DESCRIPTION_CANNOT_SCHEDULE_RECURRING_ITERATION);
1978    alerts.put(ALERT_TYPE_CANNOT_RENAME_CURRENT_TASK_FILE,
1979               ALERT_DESCRIPTION_CANNOT_RENAME_CURRENT_TASK_FILE);
1980    alerts.put(ALERT_TYPE_CANNOT_RENAME_NEW_TASK_FILE,
1981               ALERT_DESCRIPTION_CANNOT_RENAME_NEW_TASK_FILE);
1982    alerts.put(ALERT_TYPE_CANNOT_WRITE_TASK_FILE,
1983               ALERT_DESCRIPTION_CANNOT_WRITE_TASK_FILE);
1984
1985    return alerts;
1986  }
1987}