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