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 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends.pluggable; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.server.core.DirectoryServer.*; 022import static org.opends.server.util.ServerConstants.*; 023import static org.opends.server.util.StaticUtils.*; 024 025import java.io.IOException; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030import java.util.SortedSet; 031import java.util.concurrent.ExecutionException; 032import java.util.concurrent.atomic.AtomicInteger; 033 034import org.forgerock.i18n.LocalizableException; 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.ConditionResult; 041import org.forgerock.opendj.ldap.DN; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.forgerock.opendj.ldap.schema.AttributeType; 044import org.forgerock.opendj.server.config.server.PluggableBackendCfg; 045import org.forgerock.util.Reject; 046import org.opends.server.api.Backend; 047import org.opends.server.api.MonitorProvider; 048import org.opends.server.backends.RebuildConfig; 049import org.opends.server.backends.VerifyConfig; 050import org.opends.server.backends.pluggable.spi.AccessMode; 051import org.opends.server.backends.pluggable.spi.Storage; 052import org.opends.server.backends.pluggable.spi.StorageInUseException; 053import org.opends.server.backends.pluggable.spi.StorageRuntimeException; 054import org.opends.server.backends.pluggable.spi.WriteOperation; 055import org.opends.server.backends.pluggable.spi.WriteableTransaction; 056import org.opends.server.core.AddOperation; 057import org.opends.server.core.DeleteOperation; 058import org.opends.server.core.DirectoryServer; 059import org.opends.server.core.ModifyDNOperation; 060import org.opends.server.core.ModifyOperation; 061import org.opends.server.core.SearchOperation; 062import org.opends.server.core.ServerContext; 063import org.opends.server.types.BackupConfig; 064import org.opends.server.types.BackupDirectory; 065import org.opends.server.types.CanceledOperationException; 066import org.opends.server.types.DirectoryException; 067import org.opends.server.types.Entry; 068import org.opends.server.types.IndexType; 069import org.opends.server.types.InitializationException; 070import org.opends.server.types.LDIFExportConfig; 071import org.opends.server.types.LDIFImportConfig; 072import org.opends.server.types.LDIFImportResult; 073import org.opends.server.types.Operation; 074import org.opends.server.types.RestoreConfig; 075import org.opends.server.util.CollectionUtils; 076import org.opends.server.util.LDIFException; 077import org.opends.server.util.RuntimeInformation; 078 079/** 080 * This is an implementation of a Directory Server Backend which stores entries locally 081 * in a pluggable storage. 082 * 083 * @param <C> 084 * the type of the BackendCfg for the current backend 085 */ 086public abstract class BackendImpl<C extends PluggableBackendCfg> extends Backend<C> implements 087 ConfigurationChangeListener<PluggableBackendCfg> 088{ 089 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 090 091 /** The configuration of this backend. */ 092 private PluggableBackendCfg cfg; 093 /** The root container to use for this backend. */ 094 private RootContainer rootContainer; 095 096 // FIXME: this is broken. Replace with read-write lock. 097 /** A count of the total operation threads currently in the backend. */ 098 private final AtomicInteger threadTotalCount = new AtomicInteger(0); 099 /** The base DNs defined for this backend instance. */ 100 private Set<DN> baseDNs; 101 102 private MonitorProvider<?> rootContainerMonitor; 103 104 /** The underlying storage engine. */ 105 private Storage storage; 106 107 /** The controls supported by this backend. */ 108 private static final Set<String> supportedControls = CollectionUtils.newHashSet( 109 OID_SUBTREE_DELETE_CONTROL, 110 OID_PAGED_RESULTS_CONTROL, 111 OID_MANAGE_DSAIT_CONTROL, 112 OID_SERVER_SIDE_SORT_REQUEST_CONTROL, 113 OID_VLV_REQUEST_CONTROL); 114 115 private ServerContext serverContext; 116 117 /** 118 * Begin a Backend API method that accesses the {@link EntryContainer} for <code>entryDN</code> 119 * and returns it. 120 * @param operation requesting the storage 121 * @param entryDN the target DN for the operation 122 * @return <code>EntryContainer</code> where <code>entryDN</code> resides 123 */ 124 private EntryContainer accessBegin(Operation operation, DN entryDN) throws DirectoryException 125 { 126 checkRootContainerInitialized(); 127 rootContainer.checkForEnoughResources(operation); 128 EntryContainer ec = rootContainer.getEntryContainer(entryDN); 129 if (ec == null) 130 { 131 throw new DirectoryException(ResultCode.UNDEFINED, ERR_BACKEND_ENTRY_DOESNT_EXIST.get(entryDN, getBackendID())); 132 } 133 threadTotalCount.getAndIncrement(); 134 return ec; 135 } 136 137 /** End a Backend API method that accesses the EntryContainer. */ 138 private void accessEnd() 139 { 140 threadTotalCount.getAndDecrement(); 141 } 142 143 /** 144 * Wait until there are no more threads accessing the storage. It is assumed 145 * that new threads have been prevented from entering the storage at the time 146 * this method is called. 147 */ 148 private void waitUntilQuiescent() 149 { 150 while (threadTotalCount.get() > 0) 151 { 152 // Still have threads accessing the storage so sleep a little 153 try 154 { 155 Thread.sleep(500); 156 } 157 catch (InterruptedException e) 158 { 159 logger.traceException(e); 160 } 161 } 162 } 163 164 @Override 165 public void configureBackend(C cfg, ServerContext serverContext) throws ConfigException 166 { 167 Reject.ifNull(cfg, "cfg must not be null"); 168 169 this.cfg = cfg; 170 this.serverContext = serverContext; 171 baseDNs = new HashSet<>(cfg.getBaseDN()); 172 storage = new TracedStorage(configureStorage(cfg, serverContext), cfg.getBackendId()); 173 } 174 175 @Override 176 public void openBackend() throws ConfigException, InitializationException 177 { 178 if (mustOpenRootContainer()) 179 { 180 rootContainer = newRootContainer(AccessMode.READ_WRITE); 181 } 182 183 // Preload the tree cache. 184 rootContainer.preload(cfg.getPreloadTimeLimit()); 185 186 try 187 { 188 // Log an informational message about the number of entries. 189 logger.info(NOTE_BACKEND_STARTED, cfg.getBackendId(), getEntryCount()); 190 } 191 catch (StorageRuntimeException e) 192 { 193 LocalizableMessage message = WARN_GET_ENTRY_COUNT_FAILED.get(e.getMessage()); 194 throw new InitializationException(message, e); 195 } 196 197 for (DN dn : cfg.getBaseDN()) 198 { 199 try 200 { 201 DirectoryServer.registerBaseDN(dn, this, false); 202 } 203 catch (Exception e) 204 { 205 throw new InitializationException(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(dn, e), e); 206 } 207 } 208 209 // Register a monitor provider for the environment. 210 rootContainerMonitor = rootContainer.getMonitorProvider(); 211 DirectoryServer.registerMonitorProvider(rootContainerMonitor); 212 213 // Register this backend as a change listener. 214 cfg.addPluggableChangeListener(this); 215 } 216 217 @Override 218 public void closeBackend() 219 { 220 cfg.removePluggableChangeListener(this); 221 222 // Deregister our base DNs. 223 for (DN dn : rootContainer.getBaseDNs()) 224 { 225 try 226 { 227 DirectoryServer.deregisterBaseDN(dn); 228 } 229 catch (Exception e) 230 { 231 logger.traceException(e); 232 } 233 } 234 235 DirectoryServer.deregisterMonitorProvider(rootContainerMonitor); 236 237 // We presume the server will prevent more operations coming into this 238 // backend, but there may be existing operations already in the 239 // backend. We need to wait for them to finish. 240 waitUntilQuiescent(); 241 242 // Close RootContainer and Storage. 243 try 244 { 245 rootContainer.close(); 246 rootContainer = null; 247 } 248 catch (StorageRuntimeException e) 249 { 250 logger.traceException(e); 251 logger.error(ERR_DATABASE_EXCEPTION, e.getMessage()); 252 } 253 254 // Make sure the thread counts are zero for next initialization. 255 threadTotalCount.set(0); 256 257 // Log an informational message. 258 logger.info(NOTE_BACKEND_OFFLINE, cfg.getBackendId()); 259 } 260 261 @Override 262 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 263 { 264 try 265 { 266 EntryContainer ec = rootContainer.getEntryContainer(baseDNs.iterator().next()); 267 AttributeIndex ai = ec.getAttributeIndex(attributeType); 268 return ai != null ? ai.isIndexed(indexType) : false; 269 } 270 catch (Exception e) 271 { 272 logger.traceException(e); 273 return false; 274 } 275 } 276 277 @Override 278 public boolean supports(BackendOperation backendOperation) 279 { 280 switch (backendOperation) 281 { 282 case BACKUP: 283 case RESTORE: 284 // Responsibility of the underlying storage. 285 return storage.supportsBackupAndRestore(); 286 default: // INDEXING, LDIF_EXPORT, LDIF_IMPORT 287 // Responsibility of this pluggable backend. 288 return true; 289 } 290 } 291 292 @Override 293 public Set<String> getSupportedFeatures() 294 { 295 return Collections.emptySet(); 296 } 297 298 @Override 299 public Set<String> getSupportedControls() 300 { 301 return supportedControls; 302 } 303 304 @Override 305 public Set<DN> getBaseDNs() 306 { 307 return baseDNs; 308 } 309 310 @Override 311 public long getEntryCount() 312 { 313 if (rootContainer != null) 314 { 315 try 316 { 317 return rootContainer.getEntryCount(); 318 } 319 catch (Exception e) 320 { 321 logger.traceException(e); 322 } 323 } 324 return -1; 325 } 326 327 @Override 328 public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException 329 { 330 EntryContainer container; 331 try { 332 container = accessBegin(null, entryDN); 333 } 334 catch (DirectoryException de) 335 { 336 if (de.getResultCode() == ResultCode.UNDEFINED) 337 { 338 return ConditionResult.UNDEFINED; 339 } 340 throw de; 341 } 342 343 container.sharedLock.lock(); 344 try 345 { 346 return ConditionResult.valueOf(container.hasSubordinates(entryDN)); 347 } 348 catch (StorageRuntimeException e) 349 { 350 throw createDirectoryException(e); 351 } 352 finally 353 { 354 container.sharedLock.unlock(); 355 accessEnd(); 356 } 357 } 358 359 @Override 360 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 361 { 362 checkNotNull(baseDN, "baseDN must not be null"); 363 364 final EntryContainer ec = accessBegin(null, baseDN); 365 ec.sharedLock.lock(); 366 try 367 { 368 return ec.getNumberOfEntriesInBaseDN(); 369 } 370 catch (Exception e) 371 { 372 throw new DirectoryException(getServerErrorResultCode(), LocalizableMessage.raw(e.getMessage()), e); 373 } 374 finally 375 { 376 ec.sharedLock.unlock(); 377 accessEnd(); 378 } 379 } 380 381 @Override 382 public long getNumberOfChildren(DN parentDN) throws DirectoryException 383 { 384 checkNotNull(parentDN, "parentDN must not be null"); 385 EntryContainer ec; 386 387 /* 388 * Only place where we need special handling. Should return -1 instead of an 389 * error if the EntryContainer is null... 390 */ 391 try { 392 ec = accessBegin(null, parentDN); 393 } 394 catch (DirectoryException de) 395 { 396 if (de.getResultCode() == ResultCode.UNDEFINED) 397 { 398 return -1; 399 } 400 throw de; 401 } 402 403 ec.sharedLock.lock(); 404 try 405 { 406 return ec.getNumberOfChildren(parentDN); 407 } 408 catch (StorageRuntimeException e) 409 { 410 throw createDirectoryException(e); 411 } 412 finally 413 { 414 ec.sharedLock.unlock(); 415 accessEnd(); 416 } 417 } 418 419 @Override 420 public boolean entryExists(final DN entryDN) throws DirectoryException 421 { 422 EntryContainer ec = accessBegin(null, entryDN); 423 ec.sharedLock.lock(); 424 try 425 { 426 return ec.entryExists(entryDN); 427 } 428 catch (StorageRuntimeException e) 429 { 430 throw createDirectoryException(e); 431 } 432 finally 433 { 434 ec.sharedLock.unlock(); 435 accessEnd(); 436 } 437 } 438 439 @Override 440 public Entry getEntry(DN entryDN) throws DirectoryException 441 { 442 EntryContainer ec = accessBegin(null, entryDN); 443 ec.sharedLock.lock(); 444 try 445 { 446 return ec.getEntry(entryDN); 447 } 448 catch (StorageRuntimeException e) 449 { 450 throw createDirectoryException(e); 451 } 452 finally 453 { 454 ec.sharedLock.unlock(); 455 accessEnd(); 456 } 457 } 458 459 @Override 460 public void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException, CanceledOperationException 461 { 462 EntryContainer ec = accessBegin(addOperation, entry.getName()); 463 464 ec.sharedLock.lock(); 465 try 466 { 467 ec.addEntry(entry, addOperation); 468 } 469 catch (StorageRuntimeException e) 470 { 471 throw createDirectoryException(e); 472 } 473 finally 474 { 475 ec.sharedLock.unlock(); 476 accessEnd(); 477 } 478 } 479 480 @Override 481 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 482 throws DirectoryException, CanceledOperationException 483 { 484 EntryContainer ec = accessBegin(deleteOperation, entryDN); 485 486 ec.sharedLock.lock(); 487 try 488 { 489 ec.deleteEntry(entryDN, deleteOperation); 490 } 491 catch (StorageRuntimeException e) 492 { 493 throw createDirectoryException(e); 494 } 495 finally 496 { 497 ec.sharedLock.unlock(); 498 accessEnd(); 499 } 500 } 501 502 @Override 503 public void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation) 504 throws DirectoryException, CanceledOperationException 505 { 506 EntryContainer ec = accessBegin(modifyOperation, newEntry.getName()); 507 508 ec.sharedLock.lock(); 509 510 try 511 { 512 ec.replaceEntry(oldEntry, newEntry, modifyOperation); 513 } 514 catch (StorageRuntimeException e) 515 { 516 throw createDirectoryException(e); 517 } 518 finally 519 { 520 ec.sharedLock.unlock(); 521 accessEnd(); 522 } 523 } 524 525 @Override 526 public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) 527 throws DirectoryException, CanceledOperationException 528 { 529 EntryContainer currentContainer = accessBegin(modifyDNOperation, currentDN); 530 EntryContainer container = rootContainer.getEntryContainer(entry.getName()); 531 532 if (currentContainer != container) 533 { 534 accessEnd(); 535 // FIXME: No reason why we cannot implement a move between containers 536 // since the containers share the same "container" 537 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, WARN_FUNCTION_NOT_SUPPORTED.get()); 538 } 539 540 currentContainer.sharedLock.lock(); 541 try 542 { 543 currentContainer.renameEntry(currentDN, entry, modifyDNOperation); 544 } 545 catch (StorageRuntimeException e) 546 { 547 throw createDirectoryException(e); 548 } 549 finally 550 { 551 currentContainer.sharedLock.unlock(); 552 accessEnd(); 553 } 554 } 555 556 @Override 557 public void search(SearchOperation searchOperation) throws DirectoryException, CanceledOperationException 558 { 559 EntryContainer ec = accessBegin(searchOperation, searchOperation.getBaseDN()); 560 561 ec.sharedLock.lock(); 562 563 try 564 { 565 ec.search(searchOperation); 566 } 567 catch (StorageRuntimeException e) 568 { 569 throw createDirectoryException(e); 570 } 571 finally 572 { 573 ec.sharedLock.unlock(); 574 accessEnd(); 575 } 576 } 577 578 private void checkRootContainerInitialized() throws DirectoryException 579 { 580 if (rootContainer == null) 581 { 582 LocalizableMessage msg = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID()); 583 throw new DirectoryException(getServerErrorResultCode(), msg); 584 } 585 } 586 587 @Override 588 public void exportLDIF(LDIFExportConfig exportConfig) 589 throws DirectoryException 590 { 591 // If the backend already has the root container open, we must use the same 592 // underlying root container 593 boolean openRootContainer = mustOpenRootContainer(); 594 try 595 { 596 if (openRootContainer) 597 { 598 rootContainer = getReadOnlyRootContainer(); 599 } 600 601 ExportJob exportJob = new ExportJob(exportConfig); 602 exportJob.exportLDIF(rootContainer); 603 } 604 catch (IOException ioe) 605 { 606 throw new DirectoryException(getServerErrorResultCode(), ERR_EXPORT_IO_ERROR.get(ioe.getMessage()), ioe); 607 } 608 catch (StorageRuntimeException de) 609 { 610 throw createDirectoryException(de); 611 } 612 catch (ConfigException | InitializationException | LDIFException e) 613 { 614 throw new DirectoryException(getServerErrorResultCode(), e.getMessageObject(), e); 615 } 616 finally 617 { 618 closeTemporaryRootContainer(openRootContainer); 619 } 620 } 621 622 private boolean mustOpenRootContainer() 623 { 624 return rootContainer == null; 625 } 626 627 @Override 628 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 629 throws DirectoryException 630 { 631 RuntimeInformation.logInfo(); 632 633 // If the rootContainer is open, the backend is initialized by something else. 634 // We can't do import while the backend is online. 635 if (rootContainer != null) 636 { 637 throw new DirectoryException(getServerErrorResultCode(), ERR_IMPORT_BACKEND_ONLINE.get()); 638 } 639 640 try 641 { 642 try 643 { 644 if (importConfig.clearBackend()) 645 { 646 // clear all files before opening the root container 647 storage.removeStorageFiles(); 648 } 649 } 650 catch (Exception e) 651 { 652 throw new DirectoryException(getServerErrorResultCode(), ERR_REMOVE_FAIL.get(e.getMessage()), e); 653 } 654 rootContainer = newRootContainer(AccessMode.READ_WRITE); 655 rootContainer.getStorage().close(); 656 return getImportStrategy(rootContainer).importLDIF(importConfig); 657 } 658 catch (Exception e) 659 { 660 throw createDirectoryException(e); 661 } 662 finally 663 { 664 try 665 { 666 if (rootContainer != null) 667 { 668 long startTime = System.currentTimeMillis(); 669 rootContainer.close(); 670 long finishTime = System.currentTimeMillis(); 671 long closeTime = (finishTime - startTime) / 1000; 672 logger.info(NOTE_IMPORT_LDIF_ROOTCONTAINER_CLOSE, closeTime); 673 rootContainer = null; 674 } 675 676 logger.info(NOTE_IMPORT_CLOSING_DATABASE); 677 } 678 catch (StorageRuntimeException de) 679 { 680 logger.traceException(de); 681 } 682 } 683 } 684 685 private ImportStrategy getImportStrategy(final RootContainer rootContainer) 686 { 687 return new OnDiskMergeImporter.StrategyImpl(serverContext, rootContainer, cfg); 688 } 689 690 @Override 691 public long verifyBackend(VerifyConfig verifyConfig) 692 throws InitializationException, ConfigException, DirectoryException 693 { 694 // If the backend already has the root container open, we must use the same 695 // underlying root container 696 final boolean openRootContainer = mustOpenRootContainer(); 697 try 698 { 699 if (openRootContainer) 700 { 701 rootContainer = getReadOnlyRootContainer(); 702 } 703 return new VerifyJob(rootContainer, verifyConfig).verifyBackend(); 704 } 705 catch (StorageRuntimeException e) 706 { 707 throw createDirectoryException(e); 708 } 709 finally 710 { 711 closeTemporaryRootContainer(openRootContainer); 712 } 713 } 714 715 /** 716 * If a root container was opened in the calling method method as read only, 717 * close it to leave the backend in the same state. 718 */ 719 private void closeTemporaryRootContainer(boolean openRootContainer) 720 { 721 if (openRootContainer && rootContainer != null) 722 { 723 try 724 { 725 rootContainer.close(); 726 rootContainer = null; 727 } 728 catch (StorageRuntimeException e) 729 { 730 logger.traceException(e); 731 } 732 } 733 } 734 735 @Override 736 public void rebuildBackend(RebuildConfig rebuildConfig, ServerContext serverContext) 737 throws InitializationException, ConfigException, DirectoryException 738 { 739 // If the backend already has the root container open, we must use the same 740 // underlying root container 741 boolean openRootContainer = mustOpenRootContainer(); 742 743 /* 744 * If the rootContainer is open, the backend is initialized by something else. 745 * We can't do any rebuild of system indexes while others are using this backend. 746 */ 747 if (!openRootContainer && rebuildConfig.includesSystemIndex()) 748 { 749 throw new DirectoryException(getServerErrorResultCode(), ERR_REBUILD_BACKEND_ONLINE.get()); 750 } 751 752 try 753 { 754 if (openRootContainer) 755 { 756 rootContainer = newRootContainer(AccessMode.READ_WRITE); 757 } 758 getImportStrategy(rootContainer).rebuildIndex(rebuildConfig); 759 } 760 catch (InitializationException | ConfigException e) 761 { 762 throw e; 763 } 764 catch (Exception e) 765 { 766 throw createDirectoryException(e); 767 } 768 finally 769 { 770 closeTemporaryRootContainer(openRootContainer); 771 } 772 } 773 774 @Override 775 public void createBackup(BackupConfig backupConfig) throws DirectoryException 776 { 777 storage.createBackup(backupConfig); 778 } 779 780 @Override 781 public void removeBackup(BackupDirectory backupDirectory, String backupID) 782 throws DirectoryException 783 { 784 storage.removeBackup(backupDirectory, backupID); 785 } 786 787 @Override 788 public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException 789 { 790 storage.restoreBackup(restoreConfig); 791 } 792 793 /** 794 * Creates the storage engine which will be used by this pluggable backend. Implementations should 795 * create and configure a new storage engine but not open it. 796 * 797 * @param cfg 798 * the configuration object 799 * @param serverContext 800 * this Directory Server intsance's server context 801 * @return The storage engine to be used by this pluggable backend. 802 * @throws ConfigException 803 * If there is an error in the configuration. 804 */ 805 protected abstract Storage configureStorage(C cfg, ServerContext serverContext) throws ConfigException; 806 807 @Override 808 public boolean isConfigurationAcceptable(C config, List<LocalizableMessage> unacceptableReasons, 809 ServerContext serverContext) 810 { 811 return isConfigurationChangeAcceptable(config, unacceptableReasons); 812 } 813 814 @Override 815 public boolean isConfigurationChangeAcceptable(PluggableBackendCfg cfg, List<LocalizableMessage> unacceptableReasons) 816 { 817 return true; 818 } 819 820 @Override 821 public ConfigChangeResult applyConfigurationChange(final PluggableBackendCfg newCfg) 822 { 823 final ConfigChangeResult ccr = new ConfigChangeResult(); 824 try 825 { 826 if(rootContainer != null) 827 { 828 rootContainer.getStorage().write(new WriteOperation() 829 { 830 @Override 831 public void run(WriteableTransaction txn) throws Exception 832 { 833 SortedSet<DN> newBaseDNs = newCfg.getBaseDN(); 834 835 // Check for changes to the base DNs. 836 removeDeletedBaseDNs(newBaseDNs, txn); 837 if (!createNewBaseDNs(newBaseDNs, ccr, txn)) 838 { 839 return; 840 } 841 842 baseDNs = new HashSet<>(newBaseDNs); 843 844 // Put the new configuration in place. 845 cfg = newCfg; 846 } 847 }); 848 } 849 } 850 catch (Exception e) 851 { 852 ccr.setResultCode(getServerErrorResultCode()); 853 ccr.addMessage(LocalizableMessage.raw(stackTraceToSingleLineString(e))); 854 } 855 return ccr; 856 } 857 858 private void removeDeletedBaseDNs(SortedSet<DN> newBaseDNs, WriteableTransaction txn) throws DirectoryException 859 { 860 for (DN baseDN : cfg.getBaseDN()) 861 { 862 if (!newBaseDNs.contains(baseDN)) 863 { 864 // The base DN was deleted. 865 DirectoryServer.deregisterBaseDN(baseDN); 866 EntryContainer ec = rootContainer.unregisterEntryContainer(baseDN); 867 ec.close(); 868 ec.delete(txn); 869 } 870 } 871 } 872 873 private boolean createNewBaseDNs(Set<DN> newBaseDNs, ConfigChangeResult ccr, WriteableTransaction txn) 874 { 875 for (DN baseDN : newBaseDNs) 876 { 877 if (!rootContainer.getBaseDNs().contains(baseDN)) 878 { 879 try 880 { 881 // The base DN was added. 882 EntryContainer ec = rootContainer.openEntryContainer(baseDN, txn, AccessMode.READ_WRITE); 883 rootContainer.registerEntryContainer(baseDN, ec); 884 DirectoryServer.registerBaseDN(baseDN, this, false); 885 } 886 catch (Exception e) 887 { 888 logger.traceException(e); 889 890 ccr.setResultCode(getServerErrorResultCode()); 891 ccr.addMessage(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(baseDN, e)); 892 return false; 893 } 894 } 895 } 896 return true; 897 } 898 899 /** 900 * Returns a handle to the root container currently used by this backend. 901 * The rootContainer could be NULL if the backend is not initialized. 902 * 903 * @return The RootContainer object currently used by this backend. 904 */ 905 public final RootContainer getRootContainer() 906 { 907 return rootContainer; 908 } 909 910 /** 911 * Returns a new read-only handle to the root container for this backend. 912 * The caller is responsible for closing the root container after use. 913 * 914 * @return The read-only RootContainer object for this backend. 915 * 916 * @throws ConfigException If an unrecoverable problem arises during 917 * initialization. 918 * @throws InitializationException If a problem occurs during initialization 919 * that is not related to the server 920 * configuration. 921 */ 922 RootContainer getReadOnlyRootContainer() throws ConfigException, InitializationException 923 { 924 return newRootContainer(AccessMode.READ_ONLY); 925 } 926 927 /** 928 * Creates a customized DirectoryException from the StorageRuntimeException 929 * thrown by the backend. 930 * 931 * @param e 932 * The StorageRuntimeException to be converted. 933 * @return DirectoryException created from exception. 934 */ 935 private DirectoryException createDirectoryException(Throwable e) 936 { 937 if (e instanceof DirectoryException) 938 { 939 return (DirectoryException) e; 940 } 941 if (e instanceof ExecutionException) 942 { 943 return createDirectoryException(e.getCause()); 944 } 945 if (e instanceof LocalizableException) 946 { 947 return new DirectoryException(getServerErrorResultCode(), ((LocalizableException) e).getMessageObject()); 948 } 949 return new DirectoryException(getServerErrorResultCode(), LocalizableMessage.raw(e.getMessage()), e); 950 } 951 952 private RootContainer newRootContainer(AccessMode accessMode) 953 throws ConfigException, InitializationException { 954 // Open the storage 955 try { 956 final RootContainer rc = new RootContainer(getBackendID(), serverContext, storage, cfg); 957 rc.open(accessMode); 958 return rc; 959 } 960 catch (StorageInUseException e) { 961 throw new InitializationException(ERR_VERIFY_BACKEND_ONLINE.get(), e); 962 } 963 catch (StorageRuntimeException e) 964 { 965 throw new InitializationException(ERR_OPEN_ENV_FAIL.get(e.getMessage()), e); 966 } 967 } 968}