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.ModificationType; 038import org.forgerock.opendj.ldap.schema.AttributeType; 039import org.opends.messages.Severity; 040import org.opends.server.core.DirectoryServer; 041import org.opends.server.core.ServerContext; 042import org.opends.server.types.Attribute; 043import org.opends.server.types.AttributeBuilder; 044import org.opends.server.types.Attributes; 045import org.forgerock.opendj.ldap.DN; 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.toLowerCase()); 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.toLowerCase()); 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 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 putAttribute(ATTR_TASK_STATE, taskState.toString()); 504 } 505 finally 506 { 507 if (lock != null) 508 { 509 lock.unlock(); 510 } 511 } 512 } 513 514 private void putAttribute(String attrName, String attrValue) 515 { 516 Attribute attr = Attributes.create(attrName, attrValue); 517 taskEntry.putAttribute(attr.getAttributeDescription().getAttributeType(), newArrayList(attr)); 518 } 519 520 /** 521 * Sets a state for this task that is the result of a call to 522 * {@link #interruptTask(TaskState, LocalizableMessage)}. 523 * It may take this task some time to actually cancel to that 524 * actual state may differ until quiescence. 525 * 526 * @param state for this task once it has canceled whatever it is doing 527 */ 528 protected void setTaskInterruptState(TaskState state) 529 { 530 this.taskInterruptState = state; 531 } 532 533 /** 534 * Gets the interrupt state for this task that was set as a 535 * result of a call to {@link #interruptTask(TaskState, LocalizableMessage)}. 536 * 537 * @return interrupt state for this task 538 */ 539 protected TaskState getTaskInterruptState() 540 { 541 return this.taskInterruptState; 542 } 543 544 /** 545 * Returns a state for this task after processing has completed. 546 * If the task was interrupted with a call to 547 * {@link #interruptTask(TaskState, LocalizableMessage)} 548 * then that method's interruptState is returned here. Otherwise 549 * this method returns TaskState.COMPLETED_SUCCESSFULLY. It is 550 * assumed that if there were errors during task processing that 551 * task state will have been derived in some other way. 552 * 553 * @return state for this task after processing has completed 554 */ 555 protected TaskState getFinalTaskState() 556 { 557 if (this.taskInterruptState != null) 558 { 559 return this.taskInterruptState; 560 } 561 return TaskState.COMPLETED_SUCCESSFULLY; 562 } 563 564 /** 565 * Replaces an attribute values of the task entry. 566 * 567 * @param name The name of the attribute that must be replaced. 568 * 569 * @param value The value that must replace the previous values of the 570 * attribute. 571 * 572 * @throws DirectoryException When an error occurs. 573 */ 574 protected void replaceAttributeValue(String name, String value) 575 throws DirectoryException 576 { 577 // We only need to grab the entry-level lock if we don't already hold the 578 // broader scheduler lock. 579 DNLock lock = null; 580 if (!taskScheduler.holdsSchedulerLock()) 581 { 582 lock = taskScheduler.writeLockEntry(taskEntryDN); 583 } 584 try 585 { 586 Entry taskEntry = getTaskEntry(); 587 588 List<Modification> modifications = newArrayList( 589 new Modification(ModificationType.REPLACE, Attributes.create(name, value))); 590 591 taskEntry.applyModifications(modifications); 592 } 593 finally 594 { 595 if (lock != null) 596 { 597 lock.unlock(); 598 } 599 } 600 } 601 602 /** 603 * Retrieves the scheduled start time for this task, if there is one. The 604 * value returned will be in the same format as the return value for 605 * <CODE>System.currentTimeMillis()</CODE>. Any value representing a time in 606 * the past, or any negative value, should be taken to mean that the task 607 * should be considered eligible for immediate execution. 608 * 609 * @return The scheduled start time for this task. 610 */ 611 public final long getScheduledStartTime() 612 { 613 return scheduledStartTime; 614 } 615 616 /** 617 * Retrieves the time that this task actually started running, if it has 618 * started. The value returned will be in the same format as the return value 619 * for <CODE>System.currentTimeMillis()</CODE>. 620 * 621 * @return The time that this task actually started running, or -1 if it has 622 * not yet been started. 623 */ 624 public final long getActualStartTime() 625 { 626 return actualStartTime; 627 } 628 629 /** 630 * Sets the actual start time for this task and updates the associated task 631 * entry as necessary. It does not automatically persist the updated task 632 * information to disk. 633 * 634 * @param actualStartTime The actual start time to use for this task. 635 */ 636 private void setActualStartTime(long actualStartTime) 637 { 638 // We only need to grab the entry-level lock if we don't already hold the 639 // broader scheduler lock. 640 DNLock lock = null; 641 if (!taskScheduler.holdsSchedulerLock()) 642 { 643 lock = taskScheduler.writeLockEntry(taskEntryDN); 644 } 645 try 646 { 647 this.actualStartTime = actualStartTime; 648 Date d = new Date(actualStartTime); 649 putAttribute(ATTR_TASK_ACTUAL_START_TIME, StaticUtils.formatDateTimeString(d)); 650 } 651 finally 652 { 653 if (lock != null) 654 { 655 lock.unlock(); 656 } 657 } 658 } 659 660 /** 661 * Retrieves the time that this task completed all of its associated 662 * processing (regardless of whether it was successful), if it has completed. 663 * The value returned will be in the same format as the return value for 664 * <CODE>System.currentTimeMillis()</CODE>. 665 * 666 * @return The time that this task actually completed running, or -1 if it 667 * has not yet completed. 668 */ 669 public final long getCompletionTime() 670 { 671 return completionTime; 672 } 673 674 /** 675 * Sets the completion time for this task and updates the associated task 676 * entry as necessary. It does not automatically persist the updated task 677 * information to disk. 678 * 679 * @param completionTime The completion time to use for this task. 680 */ 681 protected void setCompletionTime(long completionTime) 682 { 683 // We only need to grab the entry-level lock if we don't already hold the 684 // broader scheduler lock. 685 DNLock lock = null; 686 if (!taskScheduler.holdsSchedulerLock()) 687 { 688 lock = taskScheduler.writeLockEntry(taskEntryDN); 689 } 690 try 691 { 692 this.completionTime = completionTime; 693 694 SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME); 695 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 696 Date d = new Date(completionTime); 697 putAttribute(ATTR_TASK_COMPLETION_TIME, dateFormat.format(d)); 698 } 699 finally 700 { 701 if (lock != null) 702 { 703 lock.unlock(); 704 } 705 } 706 } 707 708 /** 709 * Retrieves the set of task IDs for any tasks on which this task is 710 * dependent. This list must not be directly modified by the caller. 711 * 712 * @return The set of task IDs for any tasks on which this task is dependent. 713 */ 714 public final LinkedList<String> getDependencyIDs() 715 { 716 return dependencyIDs; 717 } 718 719 /** 720 * Retrieves the action that should be taken if any of the dependencies for 721 * this task do not complete successfully. 722 * 723 * @return The action that should be taken if any of the dependencies for 724 * this task do not complete successfully. 725 */ 726 public final FailedDependencyAction getFailedDependencyAction() 727 { 728 return failedDependencyAction; 729 } 730 731 /** 732 * Retrieves the set of e-mail addresses for the users that should receive a 733 * notification message when processing for this task has completed. This 734 * notification will be sent to these users regardless of whether the task 735 * completed successfully. This list must not be directly modified by the 736 * caller. 737 * 738 * @return The set of e-mail addresses for the users that should receive a 739 * notification message when processing for this task has 740 * completed. 741 */ 742 public final LinkedList<String> getNotifyOnCompletionAddresses() 743 { 744 return notifyOnCompletion; 745 } 746 747 /** 748 * Retrieves the set of e-mail addresses for the users that should receive a 749 * notification message if processing for this task does not complete 750 * successfully. This list must not be directly modified by the caller. 751 * 752 * @return The set of e-mail addresses for the users that should receive a 753 * notification message if processing for this task does not complete 754 * successfully. 755 */ 756 public final LinkedList<String> getNotifyOnErrorAddresses() 757 { 758 return notifyOnError; 759 } 760 761 /** 762 * Retrieves the set of messages that were logged by this task. This list 763 * must not be directly modified by the caller. 764 * 765 * @return The set of messages that were logged by this task. 766 */ 767 public final List<LocalizableMessage> getLogMessages() 768 { 769 List<LocalizableMessage> msgList = new ArrayList<>(); 770 for(String logString : logMessages) { 771 // TODO: a better job or recreating the message 772 msgList.add(LocalizableMessage.raw(logString)); 773 } 774 return Collections.unmodifiableList(msgList); 775 } 776 777 /** 778 * Adds a log message to the set of messages logged by this task. This method 779 * should not be called directly by tasks, but rather will be called 780 * indirectly through the {@code ErrorLog.logError} methods. It does not 781 * automatically persist the updated task information to disk. 782 * 783 * @param severity 784 * the severity of message. 785 * @param message 786 * the log message. 787 */ 788 public void addLogMessage(Severity severity, LocalizableMessage message) { 789 addLogMessage(severity, message, null); 790 } 791 792 /** 793 * Adds a log message to the set of messages logged by this task. This method 794 * should not be called directly by tasks, but rather will be called 795 * indirectly through the {@code ErrorLog.logError} methods. It does not 796 * automatically persist the updated task information to disk. 797 * 798 * @param severity 799 * the severity of message. 800 * @param message 801 * the log message. 802 * @param exception 803 * the exception to log. May be {@code null}. 804 */ 805 public void addLogMessage(Severity severity, LocalizableMessage message, Throwable exception) 806 { 807 // We cannot do task logging if the schema is either destroyed or 808 // not initialized eg during in-core restart from Restart task. 809 // Bailing out if there is no schema available saves us from NPE. 810 if (DirectoryServer.getSchema() == null) 811 { 812 return; 813 } 814 815 // We only need to grab the entry-level lock if we don't already hold the 816 // broader scheduler lock. 817 DNLock lock = null; 818 if (!taskScheduler.holdsSchedulerLock()) 819 { 820 lock = taskScheduler.writeLockEntry(taskEntryDN); 821 } 822 try 823 { 824 String messageString = buildLogMessage(severity, message, exception); 825 logMessages.add(messageString); 826 827 final AttributeType type = DirectoryServer.getAttributeType(ATTR_TASK_LOG_MESSAGES); 828 final Attribute attr = taskEntry.getExactAttribute(AttributeDescription.create(type)); 829 final AttributeBuilder builder = attr != null ? new AttributeBuilder(attr) : new AttributeBuilder(type); 830 builder.add(messageString); 831 taskEntry.putAttribute(type, builder.toAttributeList()); 832 } 833 finally 834 { 835 if (lock != null) 836 { 837 lock.unlock(); 838 } 839 } 840 } 841 842 private String buildLogMessage(Severity severity, LocalizableMessage message, Throwable exception) 843 { 844 StringBuilder buffer = new StringBuilder(); 845 buffer.append("["); 846 buffer.append(TimeThread.getLocalTime()); 847 buffer.append("] severity=\""); 848 buffer.append(severity.name()); 849 buffer.append("\" msgCount="); 850 buffer.append(logMessageCounter++); 851 buffer.append(" msgID="); 852 buffer.append(message.resourceName()); 853 buffer.append("-"); 854 buffer.append(message.ordinal()); 855 buffer.append(" message=\""); 856 buffer.append(message); 857 buffer.append("\""); 858 if (exception != null) 859 { 860 buffer.append(" exception=\""); 861 buffer.append(StaticUtils.stackTraceToSingleLineString(exception)); 862 buffer.append("\""); 863 } 864 return buffer.toString(); 865 } 866 867 /** 868 * Compares this task with the provided task for the purposes of ordering in a 869 * sorted list. Any completed task will always be ordered before an 870 * uncompleted task. If both tasks are completed, then they will be ordered 871 * by completion time. If both tasks are uncompleted, then a running task 872 * will always be ordered before one that has not started. If both are 873 * running, then they will be ordered by actual start time. If neither have 874 * started, then they will be ordered by scheduled start time. If all else 875 * fails, they will be ordered lexicographically by task ID. 876 * 877 * @param task The task to compare with this task. 878 * 879 * @return A negative value if the provided task should come before this 880 * task, a positive value if the provided task should come after this 881 * task, or zero if there is no difference with regard to their 882 * order. 883 */ 884 @Override 885 public final int compareTo(Task task) 886 { 887 if (completionTime > 0) 888 { 889 return compareTimes(task, completionTime, task.completionTime); 890 } 891 else if (task.completionTime > 0) 892 { 893 // Completed tasks are always ordered before those that haven't completed. 894 return 1; 895 } 896 897 if (actualStartTime > 0) 898 { 899 return compareTimes(task, actualStartTime, task.actualStartTime); 900 } 901 else if (task.actualStartTime > 0) 902 { 903 // Running tasks are always ordered before those that haven't started. 904 return 1; 905 } 906 907 // Neither task has started, so order by scheduled start time, or if nothing 908 // else by task ID. 909 if (scheduledStartTime < task.scheduledStartTime) 910 { 911 return -1; 912 } 913 else if (scheduledStartTime > task.scheduledStartTime) 914 { 915 return 1; 916 } 917 else 918 { 919 return taskID.compareTo(task.taskID); 920 } 921 } 922 923 private int compareTimes(Task task, long time1, long time2) 924 { 925 if (time2 > 0) 926 { 927 // They are both running, so order by actual start time. 928 // OR they have both completed, so order by completion time. 929 if (time1 < time2) 930 { 931 return -1; 932 } 933 else if (time1 > time2) 934 { 935 return 1; 936 } 937 else 938 { 939 // They have the same actual start/completion time, so order by task ID. 940 return taskID.compareTo(task.taskID); 941 } 942 } 943 else 944 { 945 // Running tasks are always ordered before those that haven't started. 946 // OR completed tasks are always ordered before those that haven't completed. 947 return -1; 948 } 949 } 950 951 /** 952 * Begins execution for this task. This is a wrapper around the 953 * <CODE>runTask</CODE> method that performs the appropriate set-up and 954 * tear-down. It should only be invoked by a task thread. 955 * 956 * @return The final state to use for the task. 957 */ 958 public final TaskState execute() 959 { 960 setActualStartTime(TimeThread.getTime()); 961 setTaskState(TaskState.RUNNING); 962 taskScheduler.writeState(); 963 964 try 965 { 966 return runTask(); 967 } 968 catch (Exception e) 969 { 970 logger.traceException(e); 971 logger.error(ERR_TASK_EXECUTE_FAILED, taskEntry.getName(), stackTraceToSingleLineString(e)); 972 return TaskState.STOPPED_BY_ERROR; 973 } 974 } 975 976 /** 977 * If appropriate, send an e-mail message with information about the 978 * completed task. 979 * 980 * @throws MessagingException If a problem occurs while attempting to send 981 * the message. 982 */ 983 protected void sendNotificationEMailMessage() 984 throws MessagingException 985 { 986 if (DirectoryServer.mailServerConfigured()) 987 { 988 LinkedHashSet<String> recipients = new LinkedHashSet<>(notifyOnCompletion); 989 if (! TaskState.isSuccessful(taskState)) 990 { 991 recipients.addAll(notifyOnError); 992 } 993 994 if (! recipients.isEmpty()) 995 { 996 EMailMessage message = 997 new EMailMessage(taskBackend.getNotificationSenderAddress(), 998 new ArrayList<String>(recipients), 999 taskState + " " + taskID); 1000 1001 String scheduledStartDate; 1002 if (scheduledStartTime <= 0) 1003 { 1004 scheduledStartDate = ""; 1005 } 1006 else 1007 { 1008 scheduledStartDate = new Date(scheduledStartTime).toString(); 1009 } 1010 1011 String actualStartDate = new Date(actualStartTime).toString(); 1012 String completionDate = new Date(completionTime).toString(); 1013 1014 message.setBody(INFO_TASK_COMPLETION_BODY.get( 1015 taskID, taskState, scheduledStartDate, actualStartDate, completionDate)); 1016 1017 for (String logMessage : logMessages) 1018 { 1019 message.appendToBody(logMessage); 1020 message.appendToBody("\r\n"); 1021 } 1022 1023 message.send(); 1024 } 1025 } 1026 } 1027 1028 /** 1029 * Performs any task-specific initialization that may be required before 1030 * processing can start. This default implementation does not do anything, 1031 * but subclasses may override it as necessary. This method will be called at 1032 * the time the task is scheduled, and therefore any failure in this method 1033 * will be returned to the client. 1034 * 1035 * @throws DirectoryException If a problem occurs during initialization that 1036 * should be returned to the client. 1037 */ 1038 public void initializeTask() 1039 throws DirectoryException 1040 { 1041 // No action is performed by default. 1042 } 1043 1044 /** 1045 * Performs the actual core processing for this task. This method should not 1046 * return until all processing associated with this task has completed. 1047 * 1048 * @return The final state to use for the task. 1049 */ 1050 protected abstract TaskState runTask(); 1051 1052 /** 1053 * Performs any necessary processing to prematurely interrupt the execution of 1054 * this task. By default no action is performed, but if it is feasible to 1055 * gracefully interrupt a task, then subclasses should override this method to 1056 * do so. 1057 * 1058 * Implementations of this method are expected to call 1059 * {@link #setTaskInterruptState(TaskState)} if the interruption is accepted 1060 * by this task. 1061 * 1062 * @param interruptState The state to use for the task if it is 1063 * successfully interrupted. 1064 * @param interruptReason A human-readable explanation for the cancellation. 1065 */ 1066 public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason) 1067 { 1068 // No action is performed by default. 1069 1070 // NOTE: if you implement this make sure to override isInterruptable() to return 'true' 1071 } 1072 1073 /** 1074 * Indicates whether or not this task is interruptible or not. 1075 * 1076 * @return boolean where true indicates that this task can be interrupted. 1077 */ 1078 public boolean isInterruptable() { 1079 return false; 1080 } 1081}