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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.backends.task;
028
029import java.text.SimpleDateFormat;
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.Date;
033import java.util.Iterator;
034import java.util.LinkedHashSet;
035import java.util.LinkedList;
036import java.util.List;
037import java.util.TimeZone;
038import java.util.UUID;
039
040import javax.mail.MessagingException;
041
042import org.forgerock.i18n.LocalizableMessage;
043import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
044import org.forgerock.i18n.slf4j.LocalizedLogger;
045import org.forgerock.opendj.ldap.ByteString;
046import org.forgerock.opendj.ldap.ModificationType;
047import org.opends.messages.Severity;
048import org.opends.server.core.DirectoryServer;
049import org.opends.server.core.ServerContext;
050import org.opends.server.types.*;
051import org.opends.server.types.LockManager.DNLock;
052import org.opends.server.util.EMailMessage;
053import org.opends.server.util.StaticUtils;
054import org.opends.server.util.TimeThread;
055
056import static org.opends.messages.BackendMessages.*;
057import static org.opends.server.config.ConfigConstants.*;
058import static org.opends.server.util.CollectionUtils.*;
059import static org.opends.server.util.ServerConstants.*;
060import static org.opends.server.util.StaticUtils.*;
061
062/**
063 * This class defines a task that may be executed by the task backend within the
064 * Directory Server.
065 */
066public abstract class Task implements Comparable<Task>
067{
068  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
069
070  /** The DN for the task entry. */
071  private DN taskEntryDN;
072  /** The entry that actually defines this task. */
073  private Entry taskEntry;
074
075  /** The action to take if one of the dependencies for this task does not complete successfully. */
076  private FailedDependencyAction failedDependencyAction;
077
078  /** The counter used for log messages associated with this task. */
079  private int logMessageCounter;
080
081  /** The task IDs of other tasks on which this task is dependent. */
082  private LinkedList<String> dependencyIDs;
083
084  /**
085   * A set of log messages generated by this task.
086   * TODO: convert from String to LocalizableMessage objects.
087   * Since these are stored in an entry we would need
088   * to adopt some way for writing message to string in such
089   * a way that the information could be reparsed from its
090   * string value.
091   */
092  private List<String> logMessages;
093
094  /**
095   * The set of e-mail addresses of the users to notify when the task is done
096   * running, regardless of whether it completes successfully.
097   */
098  private LinkedList<String> notifyOnCompletion;
099
100  /**
101   * The set of e-mail addresses of the users to notify if the task does not
102   * complete successfully for some reason.
103   */
104  private LinkedList<String> notifyOnError;
105
106  /** The time that processing actually started for this task. */
107  private long actualStartTime;
108  /** The time that actual processing ended for this task. */
109  private long completionTime;
110  /** The time that this task was scheduled to start processing. */
111  private long scheduledStartTime;
112
113  /** The operation used to create this task in the server. */
114  private Operation operation;
115
116  /** The ID of the recurring task with which this task is associated. */
117  private String recurringTaskID;
118
119  /** The unique ID assigned to this task. */
120  private String taskID;
121  /** The task backend with which this task is associated. */
122  private TaskBackend taskBackend;
123  /** The current state of this task. */
124  private TaskState taskState;
125  /** The task state that may be set when the task is interrupted. */
126  private TaskState taskInterruptState;
127  /** The scheduler with which this task is associated. */
128  private TaskScheduler taskScheduler;
129
130  private ServerContext serverContext;
131
132  /**
133   * Returns the server context.
134   *
135   * @return the server context.
136   */
137  protected ServerContext getServerContext()
138  {
139    return serverContext;
140  }
141
142  /**
143   * Gets a message that identifies this type of task suitable for
144   * presentation to humans in monitoring tools.
145   *
146   * @return name of task
147   */
148  public LocalizableMessage getDisplayName() {
149    // NOTE: this method is invoked via reflection.  If you rename
150    // it be sure to modify the calls.
151    return null;
152  }
153
154  /**
155   * Given an attribute type name returns and locale sensitive
156   * representation.
157   *
158   * @param name of an attribute type associated with the object
159   *        class that represents this entry in the directory
160   * @return LocalizableMessage display name
161   */
162  public LocalizableMessage getAttributeDisplayName(String name) {
163    // Subclasses that are schedulable from the task interface should override this
164
165    // NOTE: this method is invoked via reflection.  If you rename
166    // it be sure to modify the calls.
167    return null;
168  }
169
170  /**
171   * Performs generic initialization for this task based on the information in
172   * the provided task entry.
173   *
174   * @param serverContext
175   *            The server context.
176   * @param  taskScheduler  The scheduler with which this task is associated.
177   * @param  taskEntry      The entry containing the task configuration.
178   *
179   * @throws  InitializationException  If a problem occurs while performing the
180   *                                   initialization.
181   */
182  public final void initializeTaskInternal(ServerContext serverContext, TaskScheduler taskScheduler,
183                                           Entry taskEntry)
184         throws InitializationException
185  {
186    this.serverContext = serverContext;
187    this.taskScheduler = taskScheduler;
188    this.taskEntry     = taskEntry;
189    this.taskEntryDN   = taskEntry.getName();
190
191    String taskDN = taskEntryDN.toString();
192
193    taskBackend       = taskScheduler.getTaskBackend();
194
195    // Get the task ID and recurring task ID values.  At least one of them must
196    // be provided.  If it's a recurring task and there is no task ID, then
197    // generate one on the fly.
198    taskID          = getAttributeValue(ATTR_TASK_ID, false);
199    recurringTaskID = getAttributeValue(ATTR_RECURRING_TASK_ID, false);
200    if (taskID == null)
201    {
202      if (recurringTaskID == null)
203      {
204        throw new InitializationException(ERR_TASK_MISSING_ATTR.get(taskEntry.getName(), ATTR_TASK_ID));
205      }
206      taskID = UUID.randomUUID().toString();
207    }
208
209    // Get the current state from the task.  If there is none, then assume it's
210    // a new task.
211    String stateString = getAttributeValue(ATTR_TASK_STATE, false);
212    if (stateString == null)
213    {
214      taskState = TaskState.UNSCHEDULED;
215    }
216    else
217    {
218      taskState = TaskState.fromString(stateString);
219      if (taskState == null)
220      {
221        LocalizableMessage message = ERR_TASK_INVALID_STATE.get(taskDN, stateString);
222        throw new InitializationException(message);
223      }
224    }
225
226    // Get the scheduled start time for the task, if there is one.  It may be
227    // in either UTC time (a date followed by a 'Z') or in the local time zone
228    // (not followed by a 'Z').
229    scheduledStartTime = getTime(taskDN, ATTR_TASK_SCHEDULED_START_TIME, ERR_TASK_CANNOT_PARSE_SCHEDULED_START_TIME);
230
231    // Get the actual start time for the task, if there is one.
232    actualStartTime = getTime(taskDN, ATTR_TASK_ACTUAL_START_TIME, ERR_TASK_CANNOT_PARSE_ACTUAL_START_TIME);
233
234    // Get the completion time for the task, if there is one.
235    completionTime = getTime(taskDN, ATTR_TASK_COMPLETION_TIME, ERR_TASK_CANNOT_PARSE_COMPLETION_TIME);
236
237    // Get information about any dependencies that the task might have.
238    dependencyIDs = getAttributeValues(ATTR_TASK_DEPENDENCY_IDS);
239
240    failedDependencyAction = FailedDependencyAction.CANCEL;
241    String actionString = getAttributeValue(ATTR_TASK_FAILED_DEPENDENCY_ACTION,
242                                            false);
243    if (actionString != null)
244    {
245      failedDependencyAction = FailedDependencyAction.fromString(actionString);
246      if (failedDependencyAction == null)
247      {
248        failedDependencyAction = FailedDependencyAction.defaultValue();
249      }
250    }
251
252    // Get the information about the e-mail addresses to use for notification purposes
253    notifyOnCompletion = getAttributeValues(ATTR_TASK_NOTIFY_ON_COMPLETION);
254    notifyOnError      = getAttributeValues(ATTR_TASK_NOTIFY_ON_ERROR);
255
256    // Get the log messages for the task.
257    logMessages  = getAttributeValues(ATTR_TASK_LOG_MESSAGES);
258    if (logMessages != null) {
259      logMessageCounter = logMessages.size();
260    }
261  }
262
263  private long getTime(String taskDN, String attrName, Arg2<Object, Object> errorMsg) throws InitializationException
264  {
265    String timeString = getAttributeValue(attrName, false);
266    if (timeString != null)
267    {
268      SimpleDateFormat dateFormat;
269      if (timeString.endsWith("Z"))
270      {
271        dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
272        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
273      }
274      else
275      {
276        dateFormat = new SimpleDateFormat(DATE_FORMAT_COMPACT_LOCAL_TIME);
277      }
278
279      try
280      {
281        return dateFormat.parse(timeString).getTime();
282      }
283      catch (Exception e)
284      {
285        logger.traceException(e);
286
287        throw new InitializationException(errorMsg.get(timeString, taskDN), e);
288      }
289    }
290    return -1;
291  }
292
293  /**
294   * Retrieves the single value for the requested attribute as a string.
295   *
296   * @param  attributeName  The name of the attribute for which to retrieve the
297   *                        value.
298   * @param  isRequired     Indicates whether the attribute is required to have
299   *                        a value.
300   *
301   * @return  The value for the requested attribute, or <CODE>null</CODE> if it
302   *          is not present in the entry and is not required.
303   *
304   * @throws  InitializationException  If the requested attribute is not present
305   *                                   in the entry but is required, or if there
306   *                                   are multiple instances of the requested
307   *                                   attribute in the entry with different
308   *                                   sets of options, or if there are multiple
309   *                                   values for the requested attribute.
310   */
311  private String getAttributeValue(String attributeName, boolean isRequired)
312          throws InitializationException
313  {
314    List<Attribute> attrList = taskEntry.getAttribute(attributeName.toLowerCase());
315    if (attrList == null || attrList.isEmpty())
316    {
317      if (isRequired)
318      {
319        throw new InitializationException(ERR_TASK_MISSING_ATTR.get(taskEntry.getName(), attributeName));
320      }
321      return null;
322    }
323
324    if (attrList.size() > 1)
325    {
326      throw new InitializationException(ERR_TASK_MULTIPLE_ATTRS_FOR_TYPE.get(attributeName, taskEntry.getName()));
327    }
328
329    Iterator<ByteString> iterator = attrList.get(0).iterator();
330    if (! iterator.hasNext())
331    {
332      if (isRequired)
333      {
334        throw new InitializationException(ERR_TASK_NO_VALUES_FOR_ATTR.get(attributeName, taskEntry.getName()));
335      }
336      return null;
337    }
338
339    ByteString value = iterator.next();
340    if (iterator.hasNext())
341    {
342      throw new InitializationException(ERR_TASK_MULTIPLE_VALUES_FOR_ATTR.get(attributeName, taskEntry.getName()));
343    }
344    return value.toString();
345  }
346
347  /**
348   * Retrieves the values for the requested attribute as a list of strings.
349   *
350   * @param  attributeName  The name of the attribute for which to retrieve the
351   *                        values.
352   *
353   * @return  The list of values for the requested attribute, or an empty list
354   *          if the attribute does not exist or does not have any values.
355   *
356   * @throws  InitializationException  If there are multiple instances of the
357   *                                   requested attribute in the entry with
358   *                                   different sets of options.
359   */
360  private LinkedList<String> getAttributeValues(String attributeName) throws InitializationException
361  {
362    LinkedList<String> valueStrings = new LinkedList<>();
363    List<Attribute> attrList = taskEntry.getAttribute(attributeName.toLowerCase());
364    if (attrList == null || attrList.isEmpty())
365    {
366      return valueStrings;
367    }
368    if (attrList.size() > 1)
369    {
370      throw new InitializationException(ERR_TASK_MULTIPLE_ATTRS_FOR_TYPE.get(attributeName, taskEntry.getName()));
371    }
372
373    Iterator<ByteString> iterator = attrList.get(0).iterator();
374    while (iterator.hasNext())
375    {
376      valueStrings.add(iterator.next().toString());
377    }
378    return valueStrings;
379  }
380
381  /**
382   * Retrieves the DN of the entry containing the definition for this task.
383   *
384   * @return  The DN of the entry containing the definition for this task.
385   */
386  public final DN getTaskEntryDN()
387  {
388    return taskEntryDN;
389  }
390
391  /**
392   * Retrieves the entry containing the definition for this task.
393   *
394   * @return  The entry containing the definition for this task.
395   */
396  public final Entry getTaskEntry()
397  {
398    return taskEntry;
399  }
400
401  /**
402   * Retrieves the operation used to create this task in the server.  Note that
403   * this will only be available when the task is first added to the scheduler,
404   * and it should only be accessed from within the {@code initializeTask}
405   * method (and even that method should not depend on it always being
406   * available, since it will not be available if the server is restarted and
407   * the task needs to be reinitialized).
408   *
409   * @return  The operation used to create this task in the server, or
410   *          {@code null} if it is not available.
411   */
412  public final Operation getOperation()
413  {
414    return operation;
415  }
416
417  /**
418   * Specifies the operation used to create this task in the server.
419   *
420   * @param  operation  The operation used to create this task in the server.
421   */
422  public final void setOperation(Operation operation)
423  {
424    this.operation = operation;
425  }
426
427  /**
428   * Retrieves the unique identifier assigned to this task.
429   *
430   * @return  The unique identifier assigned to this task.
431   */
432  public final String getTaskID()
433  {
434    return taskID;
435  }
436
437  /**
438   * Retrieves the unique identifier assigned to the recurring task that is
439   * associated with this task, if there is one.
440   *
441   * @return  The unique identifier assigned to the recurring task that is
442   *          associated with this task, or <CODE>null</CODE> if it is not
443   *          associated with any recurring task.
444   */
445  public final String getRecurringTaskID()
446  {
447    return recurringTaskID;
448  }
449
450  /**
451   * Retrieves the current state for this task.
452   *
453   * @return  The current state for this task.
454   */
455  public final TaskState getTaskState()
456  {
457    return taskState;
458  }
459
460  /**
461   * Indicates whether or not this task is an iteration of
462   * some recurring task.
463   *
464   * @return boolean where true indicates that this task is
465   *         recurring, false otherwise.
466   */
467  public boolean isRecurring()
468  {
469    return recurringTaskID != null;
470  }
471
472  /**
473   * Indicates whether or not this task has been cancelled.
474   *
475   * @return boolean where true indicates that this task was
476   *         cancelled either before or during execution
477   */
478  public boolean isCancelled()
479  {
480    return taskInterruptState != null &&
481      TaskState.isCancelled(taskInterruptState);
482  }
483
484  /**
485   * Sets the state for this task and updates the associated task entry as
486   * necessary.  It does not automatically persist the updated task information
487   * to disk.
488   *
489   * @param  taskState  The new state to use for the task.
490   */
491  void setTaskState(TaskState taskState)
492  {
493    // We only need to grab the entry-level lock if we don't already hold the
494    // broader scheduler lock.
495    DNLock lock = null;
496    if (!taskScheduler.holdsSchedulerLock())
497    {
498      lock = taskScheduler.writeLockEntry(taskEntryDN);
499    }
500    try
501    {
502      this.taskState = taskState;
503      Attribute attr = Attributes.create(ATTR_TASK_STATE, taskState.toString());
504      taskEntry.putAttribute(attr.getAttributeType(), newArrayList(attr));
505    }
506    finally
507    {
508      if (lock != null)
509      {
510        lock.unlock();
511      }
512    }
513  }
514
515  /**
516   * Sets a state for this task that is the result of a call to
517   * {@link #interruptTask(TaskState, LocalizableMessage)}.
518   * It may take this task some time to actually cancel to that
519   * actual state may differ until quiescence.
520   *
521   * @param state for this task once it has canceled whatever it is doing
522   */
523  protected void setTaskInterruptState(TaskState state)
524  {
525    this.taskInterruptState = state;
526  }
527
528  /**
529   * Gets the interrupt state for this task that was set as a
530   * result of a call to {@link #interruptTask(TaskState, LocalizableMessage)}.
531   *
532   * @return interrupt state for this task
533   */
534  protected TaskState getTaskInterruptState()
535  {
536    return this.taskInterruptState;
537  }
538
539  /**
540   * Returns a state for this task after processing has completed.
541   * If the task was interrupted with a call to
542   * {@link #interruptTask(TaskState, LocalizableMessage)}
543   * then that method's interruptState is returned here.  Otherwise
544   * this method returns TaskState.COMPLETED_SUCCESSFULLY.  It is
545   * assumed that if there were errors during task processing that
546   * task state will have been derived in some other way.
547   *
548   * @return state for this task after processing has completed
549   */
550  protected TaskState getFinalTaskState()
551  {
552    if (this.taskInterruptState != null)
553    {
554      return this.taskInterruptState;
555    }
556    return TaskState.COMPLETED_SUCCESSFULLY;
557  }
558
559  /**
560   * Replaces an attribute values of the task entry.
561   *
562   * @param  name  The name of the attribute that must be replaced.
563   *
564   * @param  value The value that must replace the previous values of the
565   *               attribute.
566   *
567   * @throws DirectoryException When an error occurs.
568   */
569  protected void replaceAttributeValue(String name, String value)
570  throws DirectoryException
571  {
572    // We only need to grab the entry-level lock if we don't already hold the
573    // broader scheduler lock.
574    DNLock lock = null;
575    if (!taskScheduler.holdsSchedulerLock())
576    {
577      lock = taskScheduler.writeLockEntry(taskEntryDN);
578    }
579    try
580    {
581      Entry taskEntry = getTaskEntry();
582
583      List<Modification> modifications = newArrayList(
584          new Modification(ModificationType.REPLACE, Attributes.create(name, value)));
585
586      taskEntry.applyModifications(modifications);
587    }
588    finally
589    {
590      if (lock != null)
591      {
592        lock.unlock();
593      }
594    }
595  }
596
597  /**
598   * Retrieves the scheduled start time for this task, if there is one.  The
599   * value returned will be in the same format as the return value for
600   * <CODE>System.currentTimeMillis()</CODE>.  Any value representing a time in
601   * the past, or any negative value, should be taken to mean that the task
602   * should be considered eligible for immediate execution.
603   *
604   * @return  The scheduled start time for this task.
605   */
606  public final long getScheduledStartTime()
607  {
608    return scheduledStartTime;
609  }
610
611  /**
612   * Retrieves the time that this task actually started running, if it has
613   * started.  The value returned will be in the same format as the return value
614   * for <CODE>System.currentTimeMillis()</CODE>.
615   *
616   * @return  The time that this task actually started running, or -1 if it has
617   *          not yet been started.
618   */
619  public final long getActualStartTime()
620  {
621    return actualStartTime;
622  }
623
624  /**
625   * Sets the actual start time for this task and updates the associated task
626   * entry as necessary.  It does not automatically persist the updated task
627   * information to disk.
628   *
629   * @param  actualStartTime  The actual start time to use for this task.
630   */
631  private void setActualStartTime(long actualStartTime)
632  {
633    // We only need to grab the entry-level lock if we don't already hold the
634    // broader scheduler lock.
635    DNLock lock = null;
636    if (!taskScheduler.holdsSchedulerLock())
637    {
638      lock = taskScheduler.writeLockEntry(taskEntryDN);
639    }
640    try
641    {
642      this.actualStartTime = actualStartTime;
643      Date d = new Date(actualStartTime);
644      String startTimeStr = StaticUtils.formatDateTimeString(d);
645      Attribute attr = Attributes.create(ATTR_TASK_ACTUAL_START_TIME, startTimeStr);
646      taskEntry.putAttribute(attr.getAttributeType(), newArrayList(attr));
647    }
648    finally
649    {
650      if (lock != null)
651      {
652        lock.unlock();
653      }
654    }
655  }
656
657  /**
658   * Retrieves the time that this task completed all of its associated
659   * processing (regardless of whether it was successful), if it has completed.
660   * The value returned will be in the same format as the return value for
661   * <CODE>System.currentTimeMillis()</CODE>.
662   *
663   * @return  The time that this task actually completed running, or -1 if it
664   *          has not yet completed.
665   */
666  public final long getCompletionTime()
667  {
668    return completionTime;
669  }
670
671  /**
672   * Sets the completion time for this task and updates the associated task
673   * entry as necessary.  It does not automatically persist the updated task
674   * information to disk.
675   *
676   * @param  completionTime  The completion time to use for this task.
677   */
678  protected void setCompletionTime(long completionTime)
679  {
680    // We only need to grab the entry-level lock if we don't already hold the
681    // broader scheduler lock.
682    DNLock lock = null;
683    if (!taskScheduler.holdsSchedulerLock())
684    {
685      lock = taskScheduler.writeLockEntry(taskEntryDN);
686    }
687    try
688    {
689      this.completionTime = completionTime;
690
691      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
692      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
693      Date d = new Date(completionTime);
694      Attribute attr = Attributes.create(ATTR_TASK_COMPLETION_TIME, dateFormat.format(d));
695      taskEntry.putAttribute(attr.getAttributeType(), newArrayList(attr));
696    }
697    finally
698    {
699      if (lock != null)
700      {
701        lock.unlock();
702      }
703    }
704  }
705
706  /**
707   * Retrieves the set of task IDs for any tasks on which this task is
708   * dependent.  This list must not be directly modified by the caller.
709   *
710   * @return  The set of task IDs for any tasks on which this task is dependent.
711   */
712  public final LinkedList<String> getDependencyIDs()
713  {
714    return dependencyIDs;
715  }
716
717  /**
718   * Retrieves the action that should be taken if any of the dependencies for
719   * this task do not complete successfully.
720   *
721   * @return  The action that should be taken if any of the dependencies for
722   *          this task do not complete successfully.
723   */
724  public final FailedDependencyAction getFailedDependencyAction()
725  {
726    return failedDependencyAction;
727  }
728
729  /**
730   * Retrieves the set of e-mail addresses for the users that should receive a
731   * notification message when processing for this task has completed.  This
732   * notification will be sent to these users regardless of whether the task
733   * completed successfully.  This list must not be directly modified by the
734   * caller.
735   *
736   * @return  The set of e-mail addresses for the users that should receive a
737   *          notification message when processing for this task has
738   *          completed.
739   */
740  public final LinkedList<String> getNotifyOnCompletionAddresses()
741  {
742    return notifyOnCompletion;
743  }
744
745  /**
746   * Retrieves the set of e-mail addresses for the users that should receive a
747   * notification message if processing for this task does not complete
748   * successfully.  This list must not be directly modified by the caller.
749   *
750   * @return  The set of e-mail addresses for the users that should receive a
751   *          notification message if processing for this task does not complete
752   *          successfully.
753   */
754  public final LinkedList<String> getNotifyOnErrorAddresses()
755  {
756    return notifyOnError;
757  }
758
759  /**
760   * Retrieves the set of messages that were logged by this task.  This list
761   * must not be directly modified by the caller.
762   *
763   * @return  The set of messages that were logged by this task.
764   */
765  public final List<LocalizableMessage> getLogMessages()
766  {
767    List<LocalizableMessage> msgList = new ArrayList<>();
768    for(String logString : logMessages) {
769      // TODO: a better job or recreating the message
770      msgList.add(LocalizableMessage.raw(logString));
771    }
772    return Collections.unmodifiableList(msgList);
773  }
774
775  /**
776   * Adds a log message to the set of messages logged by this task. This method
777   * should not be called directly by tasks, but rather will be called
778   * indirectly through the {@code ErrorLog.logError} methods. It does not
779   * automatically persist the updated task information to disk.
780   *
781   * @param severity
782   *          the severity of message.
783   * @param message
784   *          the log message.
785   */
786  public void addLogMessage(Severity severity, LocalizableMessage message) {
787    addLogMessage(severity, message, null);
788  }
789
790  /**
791   * Adds a log message to the set of messages logged by this task. This method
792   * should not be called directly by tasks, but rather will be called
793   * indirectly through the {@code ErrorLog.logError} methods. It does not
794   * automatically persist the updated task information to disk.
795   *
796   * @param severity
797   *          the severity of message.
798   * @param message
799   *          the log message.
800   * @param exception
801   *          the exception to log. May be {@code null}.
802   */
803  public void addLogMessage(Severity severity, LocalizableMessage message, Throwable exception)
804  {
805    // We cannot do task logging if the schema is either destroyed or
806    // not initialized eg during in-core restart from Restart task.
807    // Bailing out if there is no schema available saves us from NPE.
808    if (DirectoryServer.getSchema() == null)
809    {
810      return;
811    }
812
813    // We only need to grab the entry-level lock if we don't already hold the
814    // broader scheduler lock.
815    DNLock lock = null;
816    if (!taskScheduler.holdsSchedulerLock())
817    {
818      lock = taskScheduler.writeLockEntry(taskEntryDN);
819    }
820    try
821    {
822      StringBuilder buffer = new StringBuilder();
823      buffer.append("[");
824      buffer.append(TimeThread.getLocalTime());
825      buffer.append("] severity=\"");
826      buffer.append(severity.name());
827      buffer.append("\" msgCount=");
828      buffer.append(logMessageCounter++);
829      buffer.append(" msgID=");
830      buffer.append(message.resourceName());
831      buffer.append("-");
832      buffer.append(message.ordinal());
833      buffer.append(" message=\"");
834      buffer.append(message);
835      buffer.append("\"");
836      if (exception != null)
837      {
838        buffer.append(" exception=\"");
839        buffer.append(StaticUtils.stackTraceToSingleLineString(exception));
840        buffer.append("\"");
841      }
842
843      String messageString = buffer.toString();
844      logMessages.add(messageString);
845
846      AttributeType type = DirectoryServer.getAttributeTypeOrDefault(
847          ATTR_TASK_LOG_MESSAGES.toLowerCase(), ATTR_TASK_LOG_MESSAGES);
848
849      final List<Attribute> attrList = taskEntry.getAttribute(type);
850      ByteString value = ByteString.valueOfUtf8(messageString);
851      if (attrList == null)
852      {
853        taskEntry.putAttribute(type, newArrayList(Attributes.create(type, value)));
854      }
855      else if (attrList.isEmpty())
856      {
857        attrList.add(Attributes.create(type, value));
858      }
859      else
860      {
861        AttributeBuilder builder = new AttributeBuilder(attrList.get(0));
862        builder.add(value);
863        attrList.set(0, builder.toAttribute());
864      }
865    }
866    finally
867    {
868      if (lock != null)
869      {
870        lock.unlock();
871      }
872    }
873  }
874
875  /**
876   * Compares this task with the provided task for the purposes of ordering in a
877   * sorted list.  Any completed task will always be ordered before an
878   * uncompleted task.  If both tasks are completed, then they will be ordered
879   * by completion time.  If both tasks are uncompleted, then a running task
880   * will always be ordered before one that has not started.  If both are
881   * running, then they will be ordered by actual start time.  If neither have
882   * started, then they will be ordered by scheduled start time.  If all else
883   * fails, they will be ordered lexicographically by task ID.
884   *
885   * @param  task  The task to compare with this task.
886   *
887   * @return  A negative value if the provided task should come before this
888   *          task, a positive value if the provided task should come after this
889   *          task, or zero if there is no difference with regard to their
890   *          order.
891   */
892  @Override
893  public final int compareTo(Task task)
894  {
895    if (completionTime > 0)
896    {
897      return compareTimes(task, completionTime, task.completionTime);
898    }
899    else if (task.completionTime > 0)
900    {
901      // Completed tasks are always ordered before those that haven't completed.
902      return 1;
903    }
904
905    if (actualStartTime > 0)
906    {
907      return compareTimes(task, actualStartTime, task.actualStartTime);
908    }
909    else if (task.actualStartTime > 0)
910    {
911      // Running tasks are always ordered before those that haven't started.
912      return 1;
913    }
914
915    // Neither task has started, so order by scheduled start time, or if nothing
916    // else by task ID.
917    if (scheduledStartTime < task.scheduledStartTime)
918    {
919      return -1;
920    }
921    else if (scheduledStartTime > task.scheduledStartTime)
922    {
923      return 1;
924    }
925    else
926    {
927      return taskID.compareTo(task.taskID);
928    }
929  }
930
931  private int compareTimes(Task task, long time1, long time2)
932  {
933    if (time2 > 0)
934    {
935      // They are both running, so order by actual start time.
936      // OR they have both completed, so order by completion time.
937      if (time1 < time2)
938      {
939        return -1;
940      }
941      else if (time1 > time2)
942      {
943        return 1;
944      }
945      else
946      {
947        // They have the same actual start/completion time, so order by task ID.
948        return taskID.compareTo(task.taskID);
949      }
950    }
951    else
952    {
953      // Running tasks are always ordered before those that haven't started.
954      // OR completed tasks are always ordered before those that haven't completed.
955      return -1;
956    }
957  }
958
959  /**
960   * Begins execution for this task.  This is a wrapper around the
961   * <CODE>runTask</CODE> method that performs the appropriate set-up and
962   * tear-down.   It should only be invoked by a task thread.
963   *
964   * @return  The final state to use for the task.
965   */
966  public final TaskState execute()
967  {
968    setActualStartTime(TimeThread.getTime());
969    setTaskState(TaskState.RUNNING);
970    taskScheduler.writeState();
971
972    try
973    {
974      return runTask();
975    }
976    catch (Exception e)
977    {
978      logger.traceException(e);
979      logger.error(ERR_TASK_EXECUTE_FAILED, taskEntry.getName(), stackTraceToSingleLineString(e));
980      return TaskState.STOPPED_BY_ERROR;
981    }
982  }
983
984  /**
985   * If appropriate, send an e-mail message with information about the
986   * completed task.
987   *
988   * @throws  MessagingException  If a problem occurs while attempting to send
989   *                              the message.
990   */
991  protected void sendNotificationEMailMessage()
992          throws MessagingException
993  {
994    if (DirectoryServer.mailServerConfigured())
995    {
996      LinkedHashSet<String> recipients = new LinkedHashSet<>(notifyOnCompletion);
997      if (! TaskState.isSuccessful(taskState))
998      {
999        recipients.addAll(notifyOnError);
1000      }
1001
1002      if (! recipients.isEmpty())
1003      {
1004        EMailMessage message =
1005             new EMailMessage(taskBackend.getNotificationSenderAddress(),
1006                              new ArrayList<String>(recipients),
1007                              taskState + " " + taskID);
1008
1009        String scheduledStartDate;
1010        if (scheduledStartTime <= 0)
1011        {
1012          scheduledStartDate = "";
1013        }
1014        else
1015        {
1016          scheduledStartDate = new Date(scheduledStartTime).toString();
1017        }
1018
1019        String actualStartDate = new Date(actualStartTime).toString();
1020        String completionDate  = new Date(completionTime).toString();
1021
1022        message.setBody(INFO_TASK_COMPLETION_BODY.get(
1023            taskID, taskState, scheduledStartDate, actualStartDate, completionDate));
1024
1025        for (String logMessage : logMessages)
1026        {
1027          message.appendToBody(logMessage);
1028          message.appendToBody("\r\n");
1029        }
1030
1031        message.send();
1032      }
1033    }
1034  }
1035
1036  /**
1037   * Performs any task-specific initialization that may be required before
1038   * processing can start.  This default implementation does not do anything,
1039   * but subclasses may override it as necessary.  This method will be called at
1040   * the time the task is scheduled, and therefore any failure in this method
1041   * will be returned to the client.
1042   *
1043   * @throws  DirectoryException  If a problem occurs during initialization that
1044   *                              should be returned to the client.
1045   */
1046  public void initializeTask()
1047         throws DirectoryException
1048  {
1049    // No action is performed by default.
1050  }
1051
1052  /**
1053   * Performs the actual core processing for this task.  This method should not
1054   * return until all processing associated with this task has completed.
1055   *
1056   * @return  The final state to use for the task.
1057   */
1058  protected abstract TaskState runTask();
1059
1060  /**
1061   * Performs any necessary processing to prematurely interrupt the execution of
1062   * this task.  By default no action is performed, but if it is feasible to
1063   * gracefully interrupt a task, then subclasses should override this method to
1064   * do so.
1065   *
1066   * Implementations of this method are expected to call
1067   * {@link #setTaskInterruptState(TaskState)} if the interruption is accepted
1068   * by this task.
1069   *
1070   * @param  interruptState   The state to use for the task if it is
1071   *                          successfully interrupted.
1072   * @param  interruptReason  A human-readable explanation for the cancellation.
1073   */
1074  public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason)
1075  {
1076    // No action is performed by default.
1077
1078    // NOTE:  if you implement this make sure to override isInterruptable() to return 'true'
1079  }
1080
1081  /**
1082   * Indicates whether or not this task is interruptible or not.
1083   *
1084   * @return boolean where true indicates that this task can be interrupted.
1085   */
1086  public boolean isInterruptable() {
1087    return false;
1088  }
1089}