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