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