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