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