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}