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