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