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