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