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