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 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends.task; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.server.config.ConfigConstants.*; 022import static org.opends.server.util.StaticUtils.*; 023 024import java.io.File; 025import java.io.FileFilter; 026import java.net.InetAddress; 027import java.nio.file.Path; 028import java.util.Collections; 029import java.util.GregorianCalendar; 030import java.util.Iterator; 031import java.util.List; 032import java.util.ListIterator; 033import java.util.Set; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.i18n.slf4j.LocalizedLogger; 037import org.forgerock.opendj.config.server.ConfigChangeResult; 038import org.forgerock.opendj.config.server.ConfigException; 039import org.forgerock.opendj.ldap.ByteString; 040import org.forgerock.opendj.ldap.ConditionResult; 041import org.forgerock.opendj.ldap.DN; 042import org.forgerock.opendj.ldap.ModificationType; 043import org.forgerock.opendj.ldap.ResultCode; 044import org.forgerock.opendj.ldap.SearchScope; 045import org.forgerock.opendj.ldap.schema.AttributeType; 046import org.forgerock.util.Reject; 047import org.opends.server.admin.server.ConfigurationChangeListener; 048import org.opends.server.admin.std.server.TaskBackendCfg; 049import org.opends.server.api.Backend; 050import org.opends.server.api.Backupable; 051import org.opends.server.config.ConfigEntry; 052import org.opends.server.core.AddOperation; 053import org.opends.server.core.DeleteOperation; 054import org.opends.server.core.DirectoryServer; 055import org.opends.server.core.ModifyDNOperation; 056import org.opends.server.core.ModifyOperation; 057import org.opends.server.core.SearchOperation; 058import org.opends.server.core.ServerContext; 059import org.opends.server.types.Attribute; 060import org.opends.server.types.BackupConfig; 061import org.opends.server.types.BackupDirectory; 062import org.opends.server.types.CanceledOperationException; 063import org.opends.server.types.DirectoryException; 064import org.opends.server.types.Entry; 065import org.opends.server.types.IndexType; 066import org.opends.server.types.InitializationException; 067import org.opends.server.types.LDIFExportConfig; 068import org.opends.server.types.LDIFImportConfig; 069import org.opends.server.types.LDIFImportResult; 070import org.opends.server.types.LockManager.DNLock; 071import org.opends.server.types.Modification; 072import org.opends.server.types.RestoreConfig; 073import org.opends.server.types.SearchFilter; 074import org.opends.server.util.BackupManager; 075import org.opends.server.util.LDIFException; 076import org.opends.server.util.LDIFReader; 077import org.opends.server.util.LDIFWriter; 078import org.opends.server.util.StaticUtils; 079 080/** 081 * This class provides an implementation of a Directory Server backend that may 082 * be used to execute various kinds of administrative tasks on a one-time or 083 * recurring basis. 084 */ 085public class TaskBackend 086 extends Backend<TaskBackendCfg> 087 implements ConfigurationChangeListener<TaskBackendCfg>, Backupable 088{ 089 090 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 091 092 093 094 /** The current configuration state. */ 095 private TaskBackendCfg currentConfig; 096 097 /** The DN of the configuration entry for this backend. */ 098 private DN configEntryDN; 099 100 /** 101 * The DN of the entry that will serve as the parent for all recurring task 102 * entries. 103 */ 104 private DN recurringTaskParentDN; 105 106 /** 107 * The DN of the entry that will serve as the parent for all scheduled task 108 * entries. 109 */ 110 private DN scheduledTaskParentDN; 111 112 /** The DN of the entry that will serve as the root for all task entries. */ 113 private DN taskRootDN; 114 115 /** The set of base DNs defined for this backend. */ 116 private DN[] baseDNs; 117 118 /** 119 * The length of time in seconds after a task is completed that it should be 120 * removed from the set of scheduled tasks. 121 */ 122 private long retentionTime; 123 124 /** The e-mail address to use for the sender from notification messages. */ 125 private String notificationSenderAddress; 126 127 /** The path to the task backing file. */ 128 private String taskBackingFile; 129 130 /** 131 * The task scheduler that will be responsible for actually invoking scheduled 132 * tasks. 133 */ 134 private TaskScheduler taskScheduler; 135 136 private ServerContext serverContext; 137 138 /** 139 * Creates a new backend with the provided information. All backend 140 * implementations must implement a default constructor that use 141 * <CODE>super()</CODE> to invoke this constructor. 142 */ 143 public TaskBackend() 144 { 145 super(); 146 147 // Perform all initialization in initializeBackend. 148 } 149 150 151 152 /** {@inheritDoc} */ 153 @Override 154 public void configureBackend(TaskBackendCfg cfg, ServerContext serverContext) throws ConfigException 155 { 156 Reject.ifNull(cfg); 157 this.serverContext = serverContext; 158 159 final DN[] baseDNs = new DN[cfg.getBaseDN().size()]; 160 cfg.getBaseDN().toArray(baseDNs); 161 162 ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn()); 163 164 configEntryDN = configEntry.getDN(); 165 166 167 // Make sure that the provided set of base DNs contains exactly one value. 168 // We will only allow one base for task entries. 169 if (baseDNs.length == 0) 170 { 171 throw new ConfigException(ERR_TASKBE_NO_BASE_DNS.get()); 172 } 173 else if (baseDNs.length > 1) 174 { 175 LocalizableMessage message = ERR_TASKBE_MULTIPLE_BASE_DNS.get(); 176 throw new ConfigException(message); 177 } 178 else 179 { 180 this.baseDNs = baseDNs; 181 182 taskRootDN = baseDNs[0]; 183 184 String recurringTaskBaseString = RECURRING_TASK_BASE_RDN + "," + 185 taskRootDN; 186 try 187 { 188 recurringTaskParentDN = DN.valueOf(recurringTaskBaseString); 189 } 190 catch (Exception e) 191 { 192 logger.traceException(e); 193 194 // This should never happen. 195 LocalizableMessage message = ERR_TASKBE_CANNOT_DECODE_RECURRING_TASK_BASE_DN.get( 196 recurringTaskBaseString, getExceptionMessage(e)); 197 throw new ConfigException(message, e); 198 } 199 200 String scheduledTaskBaseString = SCHEDULED_TASK_BASE_RDN + "," + 201 taskRootDN; 202 try 203 { 204 scheduledTaskParentDN = DN.valueOf(scheduledTaskBaseString); 205 } 206 catch (Exception e) 207 { 208 logger.traceException(e); 209 210 // This should never happen. 211 LocalizableMessage message = ERR_TASKBE_CANNOT_DECODE_SCHEDULED_TASK_BASE_DN.get( 212 scheduledTaskBaseString, getExceptionMessage(e)); 213 throw new ConfigException(message, e); 214 } 215 } 216 217 218 // Get the retention time that will be used to determine how long task 219 // information stays around once the associated task is completed. 220 retentionTime = cfg.getTaskRetentionTime(); 221 222 223 // Get the notification sender address. 224 notificationSenderAddress = cfg.getNotificationSenderAddress(); 225 if (notificationSenderAddress == null) 226 { 227 try 228 { 229 notificationSenderAddress = "opendj-task-notification@" + 230 InetAddress.getLocalHost().getCanonicalHostName(); 231 } 232 catch (Exception e) 233 { 234 notificationSenderAddress = "opendj-task-notification@opendj.org"; 235 } 236 } 237 238 239 // Get the path to the task data backing file. 240 taskBackingFile = cfg.getTaskBackingFile(); 241 242 currentConfig = cfg; 243 } 244 245 246 247 /** {@inheritDoc} */ 248 @Override 249 public void openBackend() 250 throws ConfigException, InitializationException 251 { 252 // Create the scheduler and initialize it from the backing file. 253 taskScheduler = new TaskScheduler(serverContext, this); 254 taskScheduler.start(); 255 256 257 // Register with the Directory Server as a configurable component. 258 currentConfig.addTaskChangeListener(this); 259 260 261 // Register the task base as a private suffix. 262 try 263 { 264 DirectoryServer.registerBaseDN(taskRootDN, this, true); 265 } 266 catch (Exception e) 267 { 268 logger.traceException(e); 269 270 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 271 taskRootDN, getExceptionMessage(e)); 272 throw new InitializationException(message, e); 273 } 274 } 275 276 277 278 /** {@inheritDoc} */ 279 @Override 280 public void closeBackend() 281 { 282 currentConfig.removeTaskChangeListener(this); 283 284 try 285 { 286 taskScheduler.stopScheduler(); 287 } 288 catch (Exception e) 289 { 290 logger.traceException(e); 291 } 292 293 try 294 { 295 LocalizableMessage message = INFO_TASKBE_INTERRUPTED_BY_SHUTDOWN.get(); 296 297 taskScheduler.interruptRunningTasks(TaskState.STOPPED_BY_SHUTDOWN, 298 message, true); 299 } 300 catch (Exception e) 301 { 302 logger.traceException(e); 303 } 304 305 try 306 { 307 DirectoryServer.deregisterBaseDN(taskRootDN); 308 } 309 catch (Exception e) 310 { 311 logger.traceException(e); 312 } 313 } 314 315 316 317 /** {@inheritDoc} */ 318 @Override 319 public DN[] getBaseDNs() 320 { 321 return baseDNs; 322 } 323 324 325 326 /** {@inheritDoc} */ 327 @Override 328 public long getEntryCount() 329 { 330 if (taskScheduler != null) 331 { 332 return taskScheduler.getEntryCount(); 333 } 334 335 return -1; 336 } 337 338 339 340 /** {@inheritDoc} */ 341 @Override 342 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 343 { 344 // All searches in this backend will always be considered indexed. 345 return true; 346 } 347 348 349 350 /** {@inheritDoc} */ 351 @Override 352 public ConditionResult hasSubordinates(DN entryDN) 353 throws DirectoryException 354 { 355 long ret = numSubordinates(entryDN, false); 356 if(ret < 0) 357 { 358 return ConditionResult.UNDEFINED; 359 } 360 return ConditionResult.valueOf(ret != 0); 361 } 362 363 /** {@inheritDoc} */ 364 @Override 365 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException { 366 checkNotNull(baseDN, "baseDN must not be null"); 367 return numSubordinates(baseDN, true) + 1; 368 } 369 370 /** {@inheritDoc} */ 371 @Override 372 public long getNumberOfChildren(DN parentDN) throws DirectoryException { 373 checkNotNull(parentDN, "parentDN must not be null"); 374 return numSubordinates(parentDN, false); 375 } 376 377 private long numSubordinates(DN entryDN, boolean subtree) throws DirectoryException 378 { 379 if (entryDN == null) 380 { 381 return -1; 382 } 383 384 if (entryDN.equals(taskRootDN)) 385 { 386 // scheduled and recurring parents. 387 if(!subtree) 388 { 389 return 2; 390 } 391 else 392 { 393 return taskScheduler.getScheduledTaskCount() + 394 taskScheduler.getRecurringTaskCount() + 2; 395 } 396 } 397 else if (entryDN.equals(scheduledTaskParentDN)) 398 { 399 return taskScheduler.getScheduledTaskCount(); 400 } 401 else if (entryDN.equals(recurringTaskParentDN)) 402 { 403 return taskScheduler.getRecurringTaskCount(); 404 } 405 406 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 407 if (parentDN == null) 408 { 409 return -1; 410 } 411 412 if (parentDN.equals(scheduledTaskParentDN) && 413 taskScheduler.getScheduledTask(entryDN) != null) 414 { 415 return 0; 416 } 417 else if (parentDN.equals(recurringTaskParentDN) && 418 taskScheduler.getRecurringTask(entryDN) != null) 419 { 420 return 0; 421 } 422 else 423 { 424 return -1; 425 } 426 } 427 428 429 430 /** {@inheritDoc} */ 431 @Override 432 public Entry getEntry(DN entryDN) 433 throws DirectoryException 434 { 435 if (entryDN == null) 436 { 437 return null; 438 } 439 440 DNLock lock = taskScheduler.readLockEntry(entryDN); 441 try 442 { 443 if (entryDN.equals(taskRootDN)) 444 { 445 return taskScheduler.getTaskRootEntry(); 446 } 447 else if (entryDN.equals(scheduledTaskParentDN)) 448 { 449 return taskScheduler.getScheduledTaskParentEntry(); 450 } 451 else if (entryDN.equals(recurringTaskParentDN)) 452 { 453 return taskScheduler.getRecurringTaskParentEntry(); 454 } 455 456 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 457 if (parentDN == null) 458 { 459 return null; 460 } 461 462 if (parentDN.equals(scheduledTaskParentDN)) 463 { 464 return taskScheduler.getScheduledTaskEntry(entryDN); 465 } 466 else if (parentDN.equals(recurringTaskParentDN)) 467 { 468 return taskScheduler.getRecurringTaskEntry(entryDN); 469 } 470 else 471 { 472 // If we've gotten here then this is not an entry 473 // that should exist in the task backend. 474 return null; 475 } 476 } 477 finally 478 { 479 lock.unlock(); 480 } 481 } 482 483 484 485 /** {@inheritDoc} */ 486 @Override 487 public void addEntry(Entry entry, AddOperation addOperation) 488 throws DirectoryException 489 { 490 Entry e = entry.duplicate(false); 491 492 // Get the DN for the entry and then get its parent. 493 DN entryDN = e.getName(); 494 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 495 496 if (parentDN == null) 497 { 498 LocalizableMessage message = ERR_TASKBE_ADD_DISALLOWED_DN. 499 get(scheduledTaskParentDN, recurringTaskParentDN); 500 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 501 } 502 503 // If the parent DN is equal to the parent for scheduled tasks, then try to 504 // treat the provided entry like a scheduled task. 505 if (parentDN.equals(scheduledTaskParentDN)) 506 { 507 Task task = taskScheduler.entryToScheduledTask(e, addOperation); 508 taskScheduler.scheduleTask(task, true); 509 return; 510 } 511 512 // If the parent DN is equal to the parent for recurring tasks, then try to 513 // treat the provided entry like a recurring task. 514 if (parentDN.equals(recurringTaskParentDN)) 515 { 516 RecurringTask recurringTask = taskScheduler.entryToRecurringTask(e); 517 taskScheduler.addRecurringTask(recurringTask, true); 518 return; 519 } 520 521 // We won't allow the entry to be added. 522 LocalizableMessage message = ERR_TASKBE_ADD_DISALLOWED_DN. 523 get(scheduledTaskParentDN, recurringTaskParentDN); 524 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 525 } 526 527 528 529 /** {@inheritDoc} */ 530 @Override 531 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 532 throws DirectoryException 533 { 534 // Get the parent for the provided entry DN. It must be either the 535 // scheduled or recurring task parent DN. 536 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 537 if (parentDN == null) 538 { 539 LocalizableMessage message = ERR_TASKBE_DELETE_INVALID_ENTRY.get(entryDN); 540 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 541 } 542 else if (parentDN.equals(scheduledTaskParentDN)) 543 { 544 // It's a scheduled task. Make sure that it exists. 545 Task t = taskScheduler.getScheduledTask(entryDN); 546 if (t == null) 547 { 548 LocalizableMessage message = ERR_TASKBE_DELETE_NO_SUCH_TASK.get(entryDN); 549 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 550 } 551 552 553 // Look at the state of the task. We will allow pending and completed 554 // tasks to be removed, but not running tasks. 555 TaskState state = t.getTaskState(); 556 if (TaskState.isPending(state)) 557 { 558 if (t.isRecurring()) { 559 taskScheduler.removePendingTask(t.getTaskID()); 560 long scheduledStartTime = t.getScheduledStartTime(); 561 long currentSystemTime = System.currentTimeMillis(); 562 if (scheduledStartTime < currentSystemTime) { 563 scheduledStartTime = currentSystemTime; 564 } 565 GregorianCalendar calendar = new GregorianCalendar(); 566 calendar.setTimeInMillis(scheduledStartTime); 567 taskScheduler.scheduleNextRecurringTaskIteration(t, 568 calendar); 569 } else { 570 taskScheduler.removePendingTask(t.getTaskID()); 571 } 572 } 573 else if (TaskState.isDone(t.getTaskState())) 574 { 575 taskScheduler.removeCompletedTask(t.getTaskID()); 576 } 577 else 578 { 579 LocalizableMessage message = ERR_TASKBE_DELETE_RUNNING.get(entryDN); 580 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 581 } 582 } 583 else if (parentDN.equals(recurringTaskParentDN)) 584 { 585 // It's a recurring task. Make sure that it exists. 586 RecurringTask rt = taskScheduler.getRecurringTask(entryDN); 587 if (rt == null) 588 { 589 LocalizableMessage message = ERR_TASKBE_DELETE_NO_SUCH_RECURRING_TASK.get(entryDN); 590 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 591 } 592 593 taskScheduler.removeRecurringTask(rt.getRecurringTaskID()); 594 } 595 else 596 { 597 LocalizableMessage message = ERR_TASKBE_DELETE_INVALID_ENTRY.get(entryDN); 598 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 599 } 600 } 601 602 603 604 /** {@inheritDoc} */ 605 @Override 606 public void replaceEntry(Entry oldEntry, Entry newEntry, 607 ModifyOperation modifyOperation) throws DirectoryException 608 { 609 DN entryDN = newEntry.getName(); 610 DNLock entryLock = null; 611 if (! taskScheduler.holdsSchedulerLock()) 612 { 613 entryLock = DirectoryServer.getLockManager().tryWriteLockEntry(entryDN); 614 if (entryLock == null) 615 { 616 throw new DirectoryException(ResultCode.BUSY, 617 ERR_TASKBE_MODIFY_CANNOT_LOCK_ENTRY.get(entryDN)); 618 } 619 } 620 621 try 622 { 623 // Get the parent for the provided entry DN. It must be either the 624 // scheduled or recurring task parent DN. 625 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 626 if (parentDN == null) 627 { 628 LocalizableMessage message = ERR_TASKBE_MODIFY_INVALID_ENTRY.get(entryDN); 629 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 630 } 631 else if (parentDN.equals(scheduledTaskParentDN)) 632 { 633 // It's a scheduled task. Make sure that it exists. 634 Task t = taskScheduler.getScheduledTask(entryDN); 635 if (t == null) 636 { 637 LocalizableMessage message = ERR_TASKBE_MODIFY_NO_SUCH_TASK.get(entryDN); 638 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 639 } 640 641 // Look at the state of the task. We will allow anything to be altered 642 // for a pending task. For a running task, we will only allow the state 643 // to be altered in order to cancel it. We will not allow any 644 // modifications for completed tasks. 645 TaskState state = t.getTaskState(); 646 if (TaskState.isPending(state) && !t.isRecurring()) 647 { 648 Task newTask = taskScheduler.entryToScheduledTask(newEntry, 649 modifyOperation); 650 taskScheduler.removePendingTask(t.getTaskID()); 651 taskScheduler.scheduleTask(newTask, true); 652 return; 653 } 654 else if (TaskState.isRunning(state)) 655 { 656 // If the task is running, we will only allow it to be cancelled. 657 // This will only be allowed using the replace modification type on 658 // the ds-task-state attribute if the value starts with "cancel" or 659 // "stop". In that case, we'll cancel the task. 660 boolean acceptable = isReplaceEntryAcceptable(modifyOperation); 661 662 if (acceptable) 663 { 664 LocalizableMessage message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get(); 665 t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message); 666 return; 667 } 668 else 669 { 670 LocalizableMessage message = ERR_TASKBE_MODIFY_RUNNING.get(entryDN); 671 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 672 } 673 } 674 else if (TaskState.isPending(state) && t.isRecurring()) 675 { 676 // Pending recurring task iterations can only be canceled. 677 boolean acceptable = isReplaceEntryAcceptable(modifyOperation); 678 if (acceptable) 679 { 680 Task newTask = taskScheduler.entryToScheduledTask(newEntry, 681 modifyOperation); 682 if (newTask.getTaskState() == 683 TaskState.CANCELED_BEFORE_STARTING) 684 { 685 taskScheduler.removePendingTask(t.getTaskID()); 686 long scheduledStartTime = t.getScheduledStartTime(); 687 long currentSystemTime = System.currentTimeMillis(); 688 if (scheduledStartTime < currentSystemTime) { 689 scheduledStartTime = currentSystemTime; 690 } 691 GregorianCalendar calendar = new GregorianCalendar(); 692 calendar.setTimeInMillis(scheduledStartTime); 693 taskScheduler.scheduleNextRecurringTaskIteration( 694 newTask, calendar); 695 } 696 else if (newTask.getTaskState() == 697 TaskState.STOPPED_BY_ADMINISTRATOR) 698 { 699 LocalizableMessage message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get(); 700 t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message); 701 } 702 return; 703 } 704 else 705 { 706 LocalizableMessage message = ERR_TASKBE_MODIFY_RECURRING.get(entryDN); 707 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 708 } 709 } 710 else 711 { 712 LocalizableMessage message = ERR_TASKBE_MODIFY_COMPLETED.get(entryDN); 713 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 714 } 715 } 716 else if (parentDN.equals(recurringTaskParentDN)) 717 { 718 // We don't currently support altering recurring tasks. 719 LocalizableMessage message = ERR_TASKBE_MODIFY_RECURRING.get(entryDN); 720 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 721 } 722 else 723 { 724 LocalizableMessage message = ERR_TASKBE_MODIFY_INVALID_ENTRY.get(entryDN); 725 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 726 } 727 } 728 finally 729 { 730 if (entryLock != null) 731 { 732 entryLock.unlock(); 733 } 734 } 735 } 736 737 738 739 /** 740 * Helper to determine if requested modifications are acceptable. 741 * @param modifyOperation associated with requested modifications. 742 * @return <CODE>true</CODE> if requested modifications are 743 * acceptable, <CODE>false</CODE> otherwise. 744 */ 745 private boolean isReplaceEntryAcceptable(ModifyOperation modifyOperation) 746 { 747 for (Modification m : modifyOperation.getModifications()) { 748 if (m.isInternal()) { 749 continue; 750 } 751 752 if (m.getModificationType() != ModificationType.REPLACE) { 753 return false; 754 } 755 756 Attribute a = m.getAttribute(); 757 AttributeType at = a.getAttributeDescription().getAttributeType(); 758 if (!at.hasName(ATTR_TASK_STATE)) { 759 return false; 760 } 761 762 Iterator<ByteString> iterator = a.iterator(); 763 if (!iterator.hasNext()) { 764 return false; 765 } 766 767 ByteString v = iterator.next(); 768 if (iterator.hasNext()) { 769 return false; 770 } 771 772 String valueString = toLowerCase(v.toString()); 773 if (!valueString.startsWith("cancel") 774 && !valueString.startsWith("stop")) { 775 return false; 776 } 777 } 778 779 return true; 780 } 781 782 783 784 /** {@inheritDoc} */ 785 @Override 786 public void renameEntry(DN currentDN, Entry entry, 787 ModifyDNOperation modifyDNOperation) 788 throws DirectoryException 789 { 790 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 791 ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); 792 } 793 794 795 796 /** {@inheritDoc} */ 797 @Override 798 public void search(SearchOperation searchOperation) 799 throws DirectoryException, CanceledOperationException { 800 // Look at the base DN and scope for the search operation to decide which 801 // entries we need to look at. 802 boolean searchRoot = false; 803 boolean searchScheduledParent = false; 804 boolean searchScheduledTasks = false; 805 boolean searchRecurringParent = false; 806 boolean searchRecurringTasks = false; 807 808 DN baseDN = searchOperation.getBaseDN(); 809 SearchScope searchScope = searchOperation.getScope(); 810 SearchFilter searchFilter = searchOperation.getFilter(); 811 812 if (baseDN.equals(taskRootDN)) 813 { 814 switch (searchScope.asEnum()) 815 { 816 case BASE_OBJECT: 817 searchRoot = true; 818 break; 819 case SINGLE_LEVEL: 820 searchScheduledParent = true; 821 searchRecurringParent = true; 822 break; 823 case WHOLE_SUBTREE: 824 searchRoot = true; 825 searchScheduledParent = true; 826 searchRecurringParent = true; 827 searchScheduledTasks = true; 828 searchRecurringTasks = true; 829 break; 830 case SUBORDINATES: 831 searchScheduledParent = true; 832 searchRecurringParent = true; 833 searchScheduledTasks = true; 834 searchRecurringTasks = true; 835 break; 836 } 837 } 838 else if (baseDN.equals(scheduledTaskParentDN)) 839 { 840 switch (searchScope.asEnum()) 841 { 842 case BASE_OBJECT: 843 searchScheduledParent = true; 844 break; 845 case SINGLE_LEVEL: 846 searchScheduledTasks = true; 847 break; 848 case WHOLE_SUBTREE: 849 searchScheduledParent = true; 850 searchScheduledTasks = true; 851 break; 852 case SUBORDINATES: 853 searchScheduledTasks = true; 854 break; 855 } 856 } 857 else if (baseDN.equals(recurringTaskParentDN)) 858 { 859 switch (searchScope.asEnum()) 860 { 861 case BASE_OBJECT: 862 searchRecurringParent = true; 863 break; 864 case SINGLE_LEVEL: 865 searchRecurringTasks = true; 866 break; 867 case WHOLE_SUBTREE: 868 searchRecurringParent = true; 869 searchRecurringTasks = true; 870 break; 871 case SUBORDINATES: 872 searchRecurringTasks = true; 873 break; 874 } 875 } 876 else 877 { 878 DN parentDN = DirectoryServer.getParentDNInSuffix(baseDN); 879 if (parentDN == null) 880 { 881 LocalizableMessage message = ERR_TASKBE_SEARCH_INVALID_BASE.get(baseDN); 882 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 883 } 884 else if (parentDN.equals(scheduledTaskParentDN)) 885 { 886 DNLock lock = taskScheduler.readLockEntry(baseDN); 887 try 888 { 889 Entry e = taskScheduler.getScheduledTaskEntry(baseDN); 890 if (e == null) 891 { 892 LocalizableMessage message = ERR_TASKBE_SEARCH_NO_SUCH_TASK.get(baseDN); 893 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 894 scheduledTaskParentDN, null); 895 } 896 897 if ((searchScope == SearchScope.BASE_OBJECT || searchScope == SearchScope.WHOLE_SUBTREE) 898 && searchFilter.matchesEntry(e)) 899 { 900 searchOperation.returnEntry(e, null); 901 } 902 903 return; 904 } 905 finally 906 { 907 lock.unlock(); 908 } 909 } 910 else if (parentDN.equals(recurringTaskParentDN)) 911 { 912 DNLock lock = taskScheduler.readLockEntry(baseDN); 913 try 914 { 915 Entry e = taskScheduler.getRecurringTaskEntry(baseDN); 916 if (e == null) 917 { 918 LocalizableMessage message = ERR_TASKBE_SEARCH_NO_SUCH_RECURRING_TASK.get(baseDN); 919 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, 920 recurringTaskParentDN, null); 921 } 922 923 if ((searchScope == SearchScope.BASE_OBJECT || searchScope == SearchScope.WHOLE_SUBTREE) 924 && searchFilter.matchesEntry(e)) 925 { 926 searchOperation.returnEntry(e, null); 927 } 928 929 return; 930 } 931 finally 932 { 933 lock.unlock(); 934 } 935 } 936 else 937 { 938 LocalizableMessage message = ERR_TASKBE_SEARCH_INVALID_BASE.get(baseDN); 939 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 940 } 941 } 942 943 944 if (searchRoot) 945 { 946 Entry e = taskScheduler.getTaskRootEntry(); 947 if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null)) 948 { 949 return; 950 } 951 } 952 953 954 if (searchScheduledParent) 955 { 956 Entry e = taskScheduler.getScheduledTaskParentEntry(); 957 if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null)) 958 { 959 return; 960 } 961 } 962 963 964 if (searchScheduledTasks 965 && !taskScheduler.searchScheduledTasks(searchOperation)) 966 { 967 return; 968 } 969 970 971 if (searchRecurringParent) 972 { 973 Entry e = taskScheduler.getRecurringTaskParentEntry(); 974 if (searchFilter.matchesEntry(e) && !searchOperation.returnEntry(e, null)) 975 { 976 return; 977 } 978 } 979 980 981 if (searchRecurringTasks 982 && !taskScheduler.searchRecurringTasks(searchOperation)) 983 { 984 return; 985 } 986 } 987 988 989 990 /** {@inheritDoc} */ 991 @Override 992 public Set<String> getSupportedControls() 993 { 994 return Collections.emptySet(); 995 } 996 997 /** {@inheritDoc} */ 998 @Override 999 public Set<String> getSupportedFeatures() 1000 { 1001 return Collections.emptySet(); 1002 } 1003 1004 /** {@inheritDoc} */ 1005 @Override 1006 public boolean supports(BackendOperation backendOperation) 1007 { 1008 switch (backendOperation) 1009 { 1010 case LDIF_EXPORT: 1011 case BACKUP: 1012 case RESTORE: 1013 return true; 1014 1015 default: 1016 return false; 1017 } 1018 } 1019 1020 /** {@inheritDoc} */ 1021 @Override 1022 public void exportLDIF(LDIFExportConfig exportConfig) 1023 throws DirectoryException 1024 { 1025 File taskFile = getFileForPath(taskBackingFile); 1026 1027 // Read from. 1028 LDIFReader ldifReader; 1029 try 1030 { 1031 ldifReader = new LDIFReader(new LDIFImportConfig(taskFile.getPath())); 1032 } 1033 catch (Exception e) 1034 { 1035 LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get(e); 1036 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 1037 } 1038 1039 // Write to. 1040 LDIFWriter ldifWriter; 1041 try 1042 { 1043 ldifWriter = new LDIFWriter(exportConfig); 1044 } 1045 catch (Exception e) 1046 { 1047 logger.traceException(e); 1048 1049 LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get( 1050 stackTraceToSingleLineString(e)); 1051 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1052 message); 1053 } 1054 1055 // Copy record by record. 1056 try 1057 { 1058 while (true) 1059 { 1060 Entry e = null; 1061 try 1062 { 1063 e = ldifReader.readEntry(); 1064 if (e == null) 1065 { 1066 break; 1067 } 1068 } 1069 catch (LDIFException le) 1070 { 1071 if (! le.canContinueReading()) 1072 { 1073 LocalizableMessage message = ERR_TASKS_CANNOT_EXPORT_TO_FILE.get(e); 1074 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, le); 1075 } 1076 continue; 1077 } 1078 ldifWriter.writeEntry(e); 1079 } 1080 } 1081 catch (Exception e) 1082 { 1083 logger.traceException(e); 1084 } 1085 finally 1086 { 1087 close(ldifWriter, ldifReader); 1088 } 1089 } 1090 1091 /** {@inheritDoc} */ 1092 @Override 1093 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext sContext) throws DirectoryException 1094 { 1095 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 1096 ERR_BACKEND_IMPORT_NOT_SUPPORTED.get(getBackendID())); 1097 } 1098 1099 /** {@inheritDoc} */ 1100 @Override 1101 public void createBackup(BackupConfig backupConfig) throws DirectoryException 1102 { 1103 new BackupManager(getBackendID()).createBackup(this, backupConfig); 1104 } 1105 1106 /** {@inheritDoc} */ 1107 @Override 1108 public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException 1109 { 1110 new BackupManager(getBackendID()).removeBackup(backupDirectory, backupID); 1111 } 1112 1113 /** {@inheritDoc} */ 1114 @Override 1115 public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException 1116 { 1117 new BackupManager(getBackendID()).restoreBackup(this, restoreConfig); 1118 } 1119 1120 /** {@inheritDoc} */ 1121 @Override 1122 public boolean isConfigurationAcceptable(TaskBackendCfg config, 1123 List<LocalizableMessage> unacceptableReasons, 1124 ServerContext serverContext) 1125 { 1126 return isConfigAcceptable(config, unacceptableReasons, null); 1127 } 1128 1129 1130 1131 /** {@inheritDoc} */ 1132 @Override 1133 public boolean isConfigurationChangeAcceptable(TaskBackendCfg configEntry, 1134 List<LocalizableMessage> unacceptableReasons) 1135 { 1136 return isConfigAcceptable(configEntry, unacceptableReasons, 1137 taskBackingFile); 1138 } 1139 1140 1141 1142 /** 1143 * Indicates whether the provided configuration is acceptable for this task 1144 * backend. 1145 * 1146 * @param config The configuration for which to make the 1147 * determination. 1148 * @param unacceptableReasons A list into which the unacceptable reasons 1149 * should be placed. 1150 * @param taskBackingFile The currently-configured task backing file, or 1151 * {@code null} if it should not be taken into 1152 * account. 1153 * 1154 * @return {@code true} if the configuration is acceptable, or {@code false} 1155 * if not. 1156 */ 1157 private static boolean isConfigAcceptable(TaskBackendCfg config, 1158 List<LocalizableMessage> unacceptableReasons, 1159 String taskBackingFile) 1160 { 1161 boolean configIsAcceptable = true; 1162 1163 1164 try 1165 { 1166 String tmpBackingFile = config.getTaskBackingFile(); 1167 if (taskBackingFile == null || 1168 !taskBackingFile.equals(tmpBackingFile)) 1169 { 1170 File f = getFileForPath(tmpBackingFile); 1171 if (f.exists()) 1172 { 1173 // This is only a problem if it's different from the active one. 1174 if (taskBackingFile != null) 1175 { 1176 unacceptableReasons.add( 1177 ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile)); 1178 configIsAcceptable = false; 1179 } 1180 } 1181 else 1182 { 1183 File p = f.getParentFile(); 1184 if (p == null) 1185 { 1186 unacceptableReasons.add(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get( 1187 tmpBackingFile)); 1188 configIsAcceptable = false; 1189 } 1190 else if (! p.exists()) 1191 { 1192 unacceptableReasons.add(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get( 1193 p.getPath(), 1194 tmpBackingFile)); 1195 configIsAcceptable = false; 1196 } 1197 else if (! p.isDirectory()) 1198 { 1199 unacceptableReasons.add( 1200 ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get( 1201 p.getPath(), 1202 tmpBackingFile)); 1203 configIsAcceptable = false; 1204 } 1205 } 1206 } 1207 } 1208 catch (Exception e) 1209 { 1210 logger.traceException(e); 1211 1212 unacceptableReasons.add(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get( 1213 getExceptionMessage(e))); 1214 1215 configIsAcceptable = false; 1216 } 1217 1218 return configIsAcceptable; 1219 } 1220 1221 1222 1223 /** {@inheritDoc} */ 1224 @Override 1225 public ConfigChangeResult applyConfigurationChange(TaskBackendCfg configEntry) 1226 { 1227 final ConfigChangeResult ccr = new ConfigChangeResult(); 1228 1229 1230 String tmpBackingFile = taskBackingFile; 1231 try 1232 { 1233 { 1234 tmpBackingFile = configEntry.getTaskBackingFile(); 1235 if (! taskBackingFile.equals(tmpBackingFile)) 1236 { 1237 File f = getFileForPath(tmpBackingFile); 1238 if (f.exists()) 1239 { 1240 ccr.addMessage(ERR_TASKBE_BACKING_FILE_EXISTS.get(tmpBackingFile)); 1241 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 1242 } 1243 else 1244 { 1245 File p = f.getParentFile(); 1246 if (p == null) 1247 { 1248 ccr.addMessage(ERR_TASKBE_INVALID_BACKING_FILE_PATH.get(tmpBackingFile)); 1249 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 1250 } 1251 else if (! p.exists()) 1252 { 1253 ccr.addMessage(ERR_TASKBE_BACKING_FILE_MISSING_PARENT.get(p, tmpBackingFile)); 1254 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 1255 } 1256 else if (! p.isDirectory()) 1257 { 1258 ccr.addMessage(ERR_TASKBE_BACKING_FILE_PARENT_NOT_DIRECTORY.get(p, tmpBackingFile)); 1259 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 1260 } 1261 } 1262 } 1263 } 1264 } 1265 catch (Exception e) 1266 { 1267 logger.traceException(e); 1268 1269 ccr.addMessage(ERR_TASKBE_ERROR_GETTING_BACKING_FILE.get(getExceptionMessage(e))); 1270 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 1271 } 1272 1273 1274 long tmpRetentionTime = configEntry.getTaskRetentionTime(); 1275 1276 1277 if (ccr.getResultCode() == ResultCode.SUCCESS) 1278 { 1279 // Everything looks OK, so apply the changes. 1280 if (retentionTime != tmpRetentionTime) 1281 { 1282 retentionTime = tmpRetentionTime; 1283 1284 ccr.addMessage(INFO_TASKBE_UPDATED_RETENTION_TIME.get(retentionTime)); 1285 } 1286 1287 1288 if (! taskBackingFile.equals(tmpBackingFile)) 1289 { 1290 taskBackingFile = tmpBackingFile; 1291 taskScheduler.writeState(); 1292 1293 ccr.addMessage(INFO_TASKBE_UPDATED_BACKING_FILE.get(taskBackingFile)); 1294 } 1295 } 1296 1297 1298 String tmpNotificationAddress = configEntry.getNotificationSenderAddress(); 1299 if (tmpNotificationAddress == null) 1300 { 1301 try 1302 { 1303 tmpNotificationAddress = "opendj-task-notification@" + 1304 InetAddress.getLocalHost().getCanonicalHostName(); 1305 } 1306 catch (Exception e) 1307 { 1308 tmpNotificationAddress = "opendj-task-notification@opendj.org"; 1309 } 1310 } 1311 notificationSenderAddress = tmpNotificationAddress; 1312 1313 1314 currentConfig = configEntry; 1315 return ccr; 1316 } 1317 1318 1319 1320 /** 1321 * Retrieves the DN of the configuration entry for this task backend. 1322 * 1323 * @return The DN of the configuration entry for this task backend. 1324 */ 1325 public DN getConfigEntryDN() 1326 { 1327 return configEntryDN; 1328 } 1329 1330 1331 1332 /** 1333 * Retrieves the path to the backing file that will hold the scheduled and 1334 * recurring task definitions. 1335 * 1336 * @return The path to the backing file that will hold the scheduled and 1337 * recurring task definitions. 1338 */ 1339 public String getTaskBackingFile() 1340 { 1341 File f = getFileForPath(taskBackingFile); 1342 return f.getPath(); 1343 } 1344 1345 1346 1347 /** 1348 * Retrieves the sender address that should be used for e-mail notifications 1349 * of task completion. 1350 * 1351 * @return The sender address that should be used for e-mail notifications of 1352 * task completion. 1353 */ 1354 public String getNotificationSenderAddress() 1355 { 1356 return notificationSenderAddress; 1357 } 1358 1359 1360 1361 /** 1362 * Retrieves the length of time in seconds that information for a task should 1363 * be retained after processing on it has completed. 1364 * 1365 * @return The length of time in seconds that information for a task should 1366 * be retained after processing on it has completed. 1367 */ 1368 public long getRetentionTime() 1369 { 1370 return retentionTime; 1371 } 1372 1373 1374 1375 /** 1376 * Retrieves the DN of the entry that is the root for all task information in 1377 * the Directory Server. 1378 * 1379 * @return The DN of the entry that is the root for all task information in 1380 * the Directory Server. 1381 */ 1382 public DN getTaskRootDN() 1383 { 1384 return taskRootDN; 1385 } 1386 1387 1388 1389 /** 1390 * Retrieves the DN of the entry that is the immediate parent for all 1391 * recurring task information in the Directory Server. 1392 * 1393 * @return The DN of the entry that is the immediate parent for all recurring 1394 * task information in the Directory Server. 1395 */ 1396 public DN getRecurringTasksParentDN() 1397 { 1398 return recurringTaskParentDN; 1399 } 1400 1401 1402 1403 /** 1404 * Retrieves the DN of the entry that is the immediate parent for all 1405 * scheduled task information in the Directory Server. 1406 * 1407 * @return The DN of the entry that is the immediate parent for all scheduled 1408 * task information in the Directory Server. 1409 */ 1410 public DN getScheduledTasksParentDN() 1411 { 1412 return scheduledTaskParentDN; 1413 } 1414 1415 1416 1417 /** 1418 * Retrieves the scheduled task for the entry with the provided DN. 1419 * 1420 * @param taskEntryDN The DN of the entry for the task to retrieve. 1421 * 1422 * @return The requested task, or {@code null} if there is no task with the 1423 * specified entry DN. 1424 */ 1425 public Task getScheduledTask(DN taskEntryDN) 1426 { 1427 return taskScheduler.getScheduledTask(taskEntryDN); 1428 } 1429 1430 1431 1432 /** 1433 * Retrieves the recurring task for the entry with the provided DN. 1434 * 1435 * @param taskEntryDN The DN of the entry for the recurring task to 1436 * retrieve. 1437 * 1438 * @return The requested recurring task, or {@code null} if there is no task 1439 * with the specified entry DN. 1440 */ 1441 public RecurringTask getRecurringTask(DN taskEntryDN) 1442 { 1443 return taskScheduler.getRecurringTask(taskEntryDN); 1444 } 1445 1446 1447 1448 /** {@inheritDoc} */ 1449 @Override 1450 public File getDirectory() 1451 { 1452 return getFileForPath(taskBackingFile).getParentFile(); 1453 } 1454 1455 private FileFilter getFilesToBackupFilter() 1456 { 1457 return new FileFilter() 1458 { 1459 @Override 1460 public boolean accept(File file) 1461 { 1462 return file.getName().equals(getFileForPath(taskBackingFile).getName()); 1463 } 1464 }; 1465 } 1466 1467 /** {@inheritDoc} */ 1468 @Override 1469 public ListIterator<Path> getFilesToBackup() throws DirectoryException 1470 { 1471 return BackupManager.getFiles(getDirectory(), getFilesToBackupFilter(), getBackendID()).listIterator(); 1472 } 1473 1474 /** {@inheritDoc} */ 1475 @Override 1476 public boolean isDirectRestore() 1477 { 1478 return true; 1479 } 1480 1481 /** {@inheritDoc} */ 1482 @Override 1483 public Path beforeRestore() throws DirectoryException 1484 { 1485 // save current files 1486 return BackupManager.saveCurrentFilesToDirectory(this, getBackendID()); 1487 } 1488 1489 /** {@inheritDoc} */ 1490 @Override 1491 public void afterRestore(Path restoreDirectory, Path saveDirectory) throws DirectoryException 1492 { 1493 // restore was successful, delete the save directory 1494 StaticUtils.recursiveDelete(saveDirectory.toFile()); 1495 } 1496 1497} 1498