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