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