001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.backends.task;
018
019import java.text.SimpleDateFormat;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Date;
023import java.util.Iterator;
024import java.util.LinkedHashSet;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.TimeZone;
028import java.util.UUID;
029
030import javax.mail.MessagingException;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.forgerock.opendj.ldap.AttributeDescription;
036import org.forgerock.opendj.ldap.ByteString;
037import org.forgerock.opendj.ldap.DN;
038import org.forgerock.opendj.ldap.ModificationType;
039import org.forgerock.opendj.ldap.schema.AttributeType;
040import org.opends.messages.Severity;
041import org.opends.server.core.DirectoryServer;
042import org.opends.server.core.ServerContext;
043import org.opends.server.types.Attribute;
044import org.opends.server.types.AttributeBuilder;
045import org.opends.server.types.Attributes;
046import org.opends.server.types.DirectoryException;
047import org.opends.server.types.Entry;
048import org.opends.server.types.InitializationException;
049import org.opends.server.types.LockManager.DNLock;
050import org.opends.server.types.Modification;
051import org.opends.server.types.Operation;
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);
315    if (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);
364    if (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 this task is an iteration of some recurring task.
462   *
463   * @return boolean where true indicates that this task is
464   *         recurring, false otherwise.
465   */
466  public boolean isRecurring()
467  {
468    return recurringTaskID != null;
469  }
470
471  /**
472   * Indicates whether this task has been cancelled.
473   *
474   * @return boolean where true indicates that this task was
475   *         cancelled either before or during execution
476   */
477  public boolean isCancelled()
478  {
479    return taskInterruptState != null &&
480      TaskState.isCancelled(taskInterruptState);
481  }
482
483  /**
484   * Sets the state for this task and updates the associated task entry as
485   * necessary.  It does not automatically persist the updated task information
486   * to disk.
487   *
488   * @param  taskState  The new state to use for the task.
489   */
490  void setTaskState(TaskState taskState)
491  {
492    // We only need to grab the entry-level lock if we don't already hold the
493    // broader scheduler lock.
494    DNLock lock = null;
495    if (!taskScheduler.holdsSchedulerLock())
496    {
497      lock = taskScheduler.writeLockEntry(taskEntryDN);
498    }
499    try
500    {
501      this.taskState = taskState;
502      putAttribute(ATTR_TASK_STATE, taskState.toString());
503    }
504    finally
505    {
506      if (lock != null)
507      {
508        lock.unlock();
509      }
510    }
511  }
512
513  private void putAttribute(String attrName, String attrValue)
514  {
515    Attribute attr = Attributes.create(attrName, attrValue);
516    taskEntry.putAttribute(attr.getAttributeDescription().getAttributeType(), newArrayList(attr));
517  }
518
519  /**
520   * Sets a state for this task that is the result of a call to
521   * {@link #interruptTask(TaskState, LocalizableMessage)}.
522   * It may take this task some time to actually cancel to that
523   * actual state may differ until quiescence.
524   *
525   * @param state for this task once it has canceled whatever it is doing
526   */
527  protected void setTaskInterruptState(TaskState state)
528  {
529    this.taskInterruptState = state;
530  }
531
532  /**
533   * Gets the interrupt state for this task that was set as a
534   * result of a call to {@link #interruptTask(TaskState, LocalizableMessage)}.
535   *
536   * @return interrupt state for this task
537   */
538  protected TaskState getTaskInterruptState()
539  {
540    return this.taskInterruptState;
541  }
542
543  /**
544   * Returns a state for this task after processing has completed.
545   * If the task was interrupted with a call to
546   * {@link #interruptTask(TaskState, LocalizableMessage)}
547   * then that method's interruptState is returned here.  Otherwise
548   * this method returns TaskState.COMPLETED_SUCCESSFULLY.  It is
549   * assumed that if there were errors during task processing that
550   * task state will have been derived in some other way.
551   *
552   * @return state for this task after processing has completed
553   */
554  protected TaskState getFinalTaskState()
555  {
556    if (this.taskInterruptState != null)
557    {
558      return this.taskInterruptState;
559    }
560    return TaskState.COMPLETED_SUCCESSFULLY;
561  }
562
563  /**
564   * Replaces an attribute values of the task entry.
565   *
566   * @param  name  The name of the attribute that must be replaced.
567   *
568   * @param  value The value that must replace the previous values of the
569   *               attribute.
570   *
571   * @throws DirectoryException When an error occurs.
572   */
573  protected void replaceAttributeValue(String name, String value)
574  throws DirectoryException
575  {
576    // We only need to grab the entry-level lock if we don't already hold the
577    // broader scheduler lock.
578    DNLock lock = null;
579    if (!taskScheduler.holdsSchedulerLock())
580    {
581      lock = taskScheduler.writeLockEntry(taskEntryDN);
582    }
583    try
584    {
585      Entry taskEntry = getTaskEntry();
586
587      List<Modification> modifications = newArrayList(
588          new Modification(ModificationType.REPLACE, Attributes.create(name, value)));
589
590      taskEntry.applyModifications(modifications);
591    }
592    finally
593    {
594      if (lock != null)
595      {
596        lock.unlock();
597      }
598    }
599  }
600
601  /**
602   * Retrieves the scheduled start time for this task, if there is one.  The
603   * value returned will be in the same format as the return value for
604   * <CODE>System.currentTimeMillis()</CODE>.  Any value representing a time in
605   * the past, or any negative value, should be taken to mean that the task
606   * should be considered eligible for immediate execution.
607   *
608   * @return  The scheduled start time for this task.
609   */
610  public final long getScheduledStartTime()
611  {
612    return scheduledStartTime;
613  }
614
615  /**
616   * Retrieves the time that this task actually started running, if it has
617   * started.  The value returned will be in the same format as the return value
618   * for <CODE>System.currentTimeMillis()</CODE>.
619   *
620   * @return  The time that this task actually started running, or -1 if it has
621   *          not yet been started.
622   */
623  public final long getActualStartTime()
624  {
625    return actualStartTime;
626  }
627
628  /**
629   * Sets the actual start time for this task and updates the associated task
630   * entry as necessary.  It does not automatically persist the updated task
631   * information to disk.
632   *
633   * @param  actualStartTime  The actual start time to use for this task.
634   */
635  private void setActualStartTime(long actualStartTime)
636  {
637    // We only need to grab the entry-level lock if we don't already hold the
638    // broader scheduler lock.
639    DNLock lock = null;
640    if (!taskScheduler.holdsSchedulerLock())
641    {
642      lock = taskScheduler.writeLockEntry(taskEntryDN);
643    }
644    try
645    {
646      this.actualStartTime = actualStartTime;
647      Date d = new Date(actualStartTime);
648      putAttribute(ATTR_TASK_ACTUAL_START_TIME, StaticUtils.formatDateTimeString(d));
649    }
650    finally
651    {
652      if (lock != null)
653      {
654        lock.unlock();
655      }
656    }
657  }
658
659  /**
660   * Retrieves the time that this task completed all of its associated
661   * processing (regardless of whether it was successful), if it has completed.
662   * The value returned will be in the same format as the return value for
663   * <CODE>System.currentTimeMillis()</CODE>.
664   *
665   * @return  The time that this task actually completed running, or -1 if it
666   *          has not yet completed.
667   */
668  public final long getCompletionTime()
669  {
670    return completionTime;
671  }
672
673  /**
674   * Sets the completion time for this task and updates the associated task
675   * entry as necessary.  It does not automatically persist the updated task
676   * information to disk.
677   *
678   * @param  completionTime  The completion time to use for this task.
679   */
680  protected void setCompletionTime(long completionTime)
681  {
682    // We only need to grab the entry-level lock if we don't already hold the
683    // broader scheduler lock.
684    DNLock lock = null;
685    if (!taskScheduler.holdsSchedulerLock())
686    {
687      lock = taskScheduler.writeLockEntry(taskEntryDN);
688    }
689    try
690    {
691      this.completionTime = completionTime;
692
693      SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
694      dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
695      Date d = new Date(completionTime);
696      putAttribute(ATTR_TASK_COMPLETION_TIME, dateFormat.format(d));
697    }
698    finally
699    {
700      if (lock != null)
701      {
702        lock.unlock();
703      }
704    }
705  }
706
707  /**
708   * Retrieves the set of task IDs for any tasks on which this task is
709   * dependent.  This list must not be directly modified by the caller.
710   *
711   * @return  The set of task IDs for any tasks on which this task is dependent.
712   */
713  public final LinkedList<String> getDependencyIDs()
714  {
715    return dependencyIDs;
716  }
717
718  /**
719   * Retrieves the action that should be taken if any of the dependencies for
720   * this task do not complete successfully.
721   *
722   * @return  The action that should be taken if any of the dependencies for
723   *          this task do not complete successfully.
724   */
725  public final FailedDependencyAction getFailedDependencyAction()
726  {
727    return failedDependencyAction;
728  }
729
730  /**
731   * Retrieves the set of e-mail addresses for the users that should receive a
732   * notification message when processing for this task has completed.  This
733   * notification will be sent to these users regardless of whether the task
734   * completed successfully.  This list must not be directly modified by the
735   * caller.
736   *
737   * @return  The set of e-mail addresses for the users that should receive a
738   *          notification message when processing for this task has
739   *          completed.
740   */
741  public final LinkedList<String> getNotifyOnCompletionAddresses()
742  {
743    return notifyOnCompletion;
744  }
745
746  /**
747   * Retrieves the set of e-mail addresses for the users that should receive a
748   * notification message if processing for this task does not complete
749   * successfully.  This list must not be directly modified by the caller.
750   *
751   * @return  The set of e-mail addresses for the users that should receive a
752   *          notification message if processing for this task does not complete
753   *          successfully.
754   */
755  public final LinkedList<String> getNotifyOnErrorAddresses()
756  {
757    return notifyOnError;
758  }
759
760  /**
761   * Retrieves the set of messages that were logged by this task.  This list
762   * must not be directly modified by the caller.
763   *
764   * @return  The set of messages that were logged by this task.
765   */
766  public final List<LocalizableMessage> getLogMessages()
767  {
768    List<LocalizableMessage> msgList = new ArrayList<>();
769    for(String logString : logMessages) {
770      // TODO: a better job or recreating the message
771      msgList.add(LocalizableMessage.raw(logString));
772    }
773    return Collections.unmodifiableList(msgList);
774  }
775
776  /**
777   * Adds a log message to the set of messages logged by this task. This method
778   * should not be called directly by tasks, but rather will be called
779   * indirectly through the {@code ErrorLog.logError} methods. It does not
780   * automatically persist the updated task information to disk.
781   *
782   * @param severity
783   *          the severity of message.
784   * @param message
785   *          the log message.
786   */
787  public void addLogMessage(Severity severity, LocalizableMessage message) {
788    addLogMessage(severity, message, null);
789  }
790
791  /**
792   * Adds a log message to the set of messages logged by this task. This method
793   * should not be called directly by tasks, but rather will be called
794   * indirectly through the {@code ErrorLog.logError} methods. It does not
795   * automatically persist the updated task information to disk.
796   *
797   * @param severity
798   *          the severity of message.
799   * @param message
800   *          the log message.
801   * @param exception
802   *          the exception to log. May be {@code null}.
803   */
804  public void addLogMessage(Severity severity, LocalizableMessage message, Throwable exception)
805  {
806    // We cannot do task logging if the schema is either destroyed or
807    // not initialized eg during in-core restart from Restart task.
808    // Bailing out if there is no schema available saves us from NPE.
809    if (DirectoryServer.getSchema() == null)
810    {
811      return;
812    }
813
814    // We only need to grab the entry-level lock if we don't already hold the
815    // broader scheduler lock.
816    DNLock lock = null;
817    if (!taskScheduler.holdsSchedulerLock())
818    {
819      lock = taskScheduler.writeLockEntry(taskEntryDN);
820    }
821    try
822    {
823      String messageString = buildLogMessage(severity, message, exception);
824      logMessages.add(messageString);
825
826      final AttributeType type = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_LOG_MESSAGES);
827      final Attribute attr = taskEntry.getExactAttribute(AttributeDescription.create(type));
828      final AttributeBuilder builder = attr != null ? new AttributeBuilder(attr) : new AttributeBuilder(type);
829      builder.add(messageString);
830      taskEntry.putAttribute(type, builder.toAttributeList());
831    }
832    finally
833    {
834      if (lock != null)
835      {
836        lock.unlock();
837      }
838    }
839  }
840
841  private String buildLogMessage(Severity severity, LocalizableMessage message, Throwable exception)
842  {
843    StringBuilder buffer = new StringBuilder();
844    buffer.append("[");
845    buffer.append(TimeThread.getLocalTime());
846    buffer.append("] severity=\"");
847    buffer.append(severity.name());
848    buffer.append("\" msgCount=");
849    buffer.append(logMessageCounter++);
850    buffer.append(" msgID=");
851    buffer.append(message.resourceName());
852    buffer.append("-");
853    buffer.append(message.ordinal());
854    buffer.append(" message=\"");
855    buffer.append(message);
856    buffer.append("\"");
857    if (exception != null)
858    {
859      buffer.append(" exception=\"");
860      buffer.append(StaticUtils.stackTraceToSingleLineString(exception));
861      buffer.append("\"");
862    }
863    return buffer.toString();
864  }
865
866  /**
867   * Compares this task with the provided task for the purposes of ordering in a
868   * sorted list.  Any completed task will always be ordered before an
869   * uncompleted task.  If both tasks are completed, then they will be ordered
870   * by completion time.  If both tasks are uncompleted, then a running task
871   * will always be ordered before one that has not started.  If both are
872   * running, then they will be ordered by actual start time.  If neither have
873   * started, then they will be ordered by scheduled start time.  If all else
874   * fails, they will be ordered lexicographically by task ID.
875   *
876   * @param  task  The task to compare with this task.
877   *
878   * @return  A negative value if the provided task should come before this
879   *          task, a positive value if the provided task should come after this
880   *          task, or zero if there is no difference with regard to their
881   *          order.
882   */
883  @Override
884  public final int compareTo(Task task)
885  {
886    if (completionTime > 0)
887    {
888      return compareTimes(task, completionTime, task.completionTime);
889    }
890    else if (task.completionTime > 0)
891    {
892      // Completed tasks are always ordered before those that haven't completed.
893      return 1;
894    }
895
896    if (actualStartTime > 0)
897    {
898      return compareTimes(task, actualStartTime, task.actualStartTime);
899    }
900    else if (task.actualStartTime > 0)
901    {
902      // Running tasks are always ordered before those that haven't started.
903      return 1;
904    }
905
906    // Neither task has started, so order by scheduled start time, or if nothing
907    // else by task ID.
908    if (scheduledStartTime < task.scheduledStartTime)
909    {
910      return -1;
911    }
912    else if (scheduledStartTime > task.scheduledStartTime)
913    {
914      return 1;
915    }
916    else
917    {
918      return taskID.compareTo(task.taskID);
919    }
920  }
921
922  private int compareTimes(Task task, long time1, long time2)
923  {
924    if (time2 > 0)
925    {
926      // They are both running, so order by actual start time.
927      // OR they have both completed, so order by completion time.
928      if (time1 < time2)
929      {
930        return -1;
931      }
932      else if (time1 > time2)
933      {
934        return 1;
935      }
936      else
937      {
938        // They have the same actual start/completion time, so order by task ID.
939        return taskID.compareTo(task.taskID);
940      }
941    }
942    else
943    {
944      // Running tasks are always ordered before those that haven't started.
945      // OR completed tasks are always ordered before those that haven't completed.
946      return -1;
947    }
948  }
949
950  /**
951   * Begins execution for this task.  This is a wrapper around the
952   * <CODE>runTask</CODE> method that performs the appropriate set-up and
953   * tear-down.   It should only be invoked by a task thread.
954   *
955   * @return  The final state to use for the task.
956   */
957  public final TaskState execute()
958  {
959    setActualStartTime(TimeThread.getTime());
960    setTaskState(TaskState.RUNNING);
961    taskScheduler.writeState();
962
963    try
964    {
965      return runTask();
966    }
967    catch (Exception e)
968    {
969      logger.traceException(e);
970      logger.error(ERR_TASK_EXECUTE_FAILED, taskEntry.getName(), stackTraceToSingleLineString(e));
971      return TaskState.STOPPED_BY_ERROR;
972    }
973  }
974
975  /**
976   * If appropriate, send an e-mail message with information about the
977   * completed task.
978   *
979   * @throws  MessagingException  If a problem occurs while attempting to send
980   *                              the message.
981   */
982  protected void sendNotificationEMailMessage()
983          throws MessagingException
984  {
985    if (DirectoryServer.mailServerConfigured())
986    {
987      LinkedHashSet<String> recipients = new LinkedHashSet<>(notifyOnCompletion);
988      if (! TaskState.isSuccessful(taskState))
989      {
990        recipients.addAll(notifyOnError);
991      }
992
993      if (! recipients.isEmpty())
994      {
995        EMailMessage message =
996             new EMailMessage(taskBackend.getNotificationSenderAddress(),
997                              new ArrayList<String>(recipients),
998                              taskState + " " + taskID);
999
1000        String scheduledStartDate;
1001        if (scheduledStartTime <= 0)
1002        {
1003          scheduledStartDate = "";
1004        }
1005        else
1006        {
1007          scheduledStartDate = new Date(scheduledStartTime).toString();
1008        }
1009
1010        String actualStartDate = new Date(actualStartTime).toString();
1011        String completionDate  = new Date(completionTime).toString();
1012
1013        message.setBody(INFO_TASK_COMPLETION_BODY.get(
1014            taskID, taskState, scheduledStartDate, actualStartDate, completionDate));
1015
1016        for (String logMessage : logMessages)
1017        {
1018          message.appendToBody(logMessage);
1019          message.appendToBody("\r\n");
1020        }
1021
1022        message.send();
1023      }
1024    }
1025  }
1026
1027  /**
1028   * Performs any task-specific initialization that may be required before
1029   * processing can start.  This default implementation does not do anything,
1030   * but subclasses may override it as necessary.  This method will be called at
1031   * the time the task is scheduled, and therefore any failure in this method
1032   * will be returned to the client.
1033   *
1034   * @throws  DirectoryException  If a problem occurs during initialization that
1035   *                              should be returned to the client.
1036   */
1037  public void initializeTask()
1038         throws DirectoryException
1039  {
1040    // No action is performed by default.
1041  }
1042
1043  /**
1044   * Performs the actual core processing for this task.  This method should not
1045   * return until all processing associated with this task has completed.
1046   *
1047   * @return  The final state to use for the task.
1048   */
1049  protected abstract TaskState runTask();
1050
1051  /**
1052   * Performs any necessary processing to prematurely interrupt the execution of
1053   * this task.  By default no action is performed, but if it is feasible to
1054   * gracefully interrupt a task, then subclasses should override this method to
1055   * do so.
1056   *
1057   * Implementations of this method are expected to call
1058   * {@link #setTaskInterruptState(TaskState)} if the interruption is accepted
1059   * by this task.
1060   *
1061   * @param  interruptState   The state to use for the task if it is
1062   *                          successfully interrupted.
1063   * @param  interruptReason  A human-readable explanation for the cancellation.
1064   */
1065  public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason)
1066  {
1067    // No action is performed by default.
1068
1069    // NOTE:  if you implement this make sure to override isInterruptable() to return 'true'
1070  }
1071
1072  /**
1073   * Indicates whether this task is interruptible or not.
1074   *
1075   * @return boolean where true indicates that this task can be interrupted.
1076   */
1077  public boolean isInterruptable() {
1078    return false;
1079  }
1080}