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}