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