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