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