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 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2017 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import static org.forgerock.opendj.ldap.ResultCode.*;
020import static org.opends.messages.ConfigMessages.*;
021import static org.opends.server.core.DirectoryServer.*;
022import static org.opends.server.util.StaticUtils.*;
023
024import java.util.Collection;
025import java.util.Iterator;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.i18n.slf4j.LocalizedLogger;
033import org.forgerock.opendj.config.server.ConfigChangeResult;
034import org.forgerock.opendj.config.server.ConfigException;
035import org.forgerock.opendj.config.server.ConfigurationAddListener;
036import org.forgerock.opendj.config.server.ConfigurationChangeListener;
037import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
038import org.forgerock.opendj.ldap.DN;
039import org.forgerock.opendj.ldap.ResultCode;
040import org.forgerock.opendj.server.config.meta.BackendCfgDefn;
041import org.forgerock.opendj.server.config.server.BackendCfg;
042import org.forgerock.opendj.server.config.server.RootCfg;
043import org.opends.server.api.Backend;
044import org.opends.server.api.BackendInitializationListener;
045import org.opends.server.backends.ConfigurationBackend;
046import org.opends.server.config.ConfigConstants;
047import org.opends.server.types.DirectoryException;
048import org.opends.server.types.Entry;
049import org.opends.server.types.InitializationException;
050import org.opends.server.types.WritabilityMode;
051
052/**
053 * This class defines a utility that will be used to manage the configuration
054 * for the set of backends defined in the Directory Server.  It will perform
055 * the necessary initialization of those backends when the server is first
056 * started, and then will manage any changes to them while the server is
057 * running.
058 */
059public class BackendConfigManager implements
060     ConfigurationChangeListener<BackendCfg>,
061     ConfigurationAddListener<BackendCfg>,
062     ConfigurationDeleteListener<BackendCfg>
063{
064  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
065
066  /** The mapping between configuration entry DNs and their corresponding backend implementations. */
067  private final ConcurrentHashMap<DN, Backend<? extends BackendCfg>> registeredBackends = new ConcurrentHashMap<>();
068  private final ServerContext serverContext;
069
070  /**
071   * Creates a new instance of this backend config manager.
072   *
073   * @param serverContext
074   *            The server context.
075   */
076  public BackendConfigManager(ServerContext serverContext)
077  {
078    this.serverContext = serverContext;
079  }
080
081  /**
082   * Initializes the configuration associated with the Directory Server
083   * backends. This should only be called at Directory Server startup.
084   *
085   * @param backendIDsToStart
086   *           The list of backendID to start. Everything will be started if empty.
087   * @throws ConfigException
088   *           If a critical configuration problem prevents the backend
089   *           initialization from succeeding.
090   * @throws InitializationException
091   *           If a problem occurs while initializing the backends that is not
092   *           related to the server configuration.
093   */
094  public void initializeBackendConfig(Collection<String> backendIDsToStart)
095         throws ConfigException, InitializationException
096  {
097    initializeConfigurationBackend();
098
099    // Register add and delete listeners.
100    RootCfg root = serverContext.getRootConfig();
101    root.addBackendAddListener(this);
102    root.addBackendDeleteListener(this);
103
104    // Get the configuration entry that is at the root of all the backends in
105    // the server.
106    Entry backendRoot;
107    try
108    {
109      DN configEntryDN = DN.valueOf(ConfigConstants.DN_BACKEND_BASE);
110      backendRoot   = DirectoryServer.getConfigEntry(configEntryDN);
111    }
112    catch (Exception e)
113    {
114      logger.traceException(e);
115
116      LocalizableMessage message =
117          ERR_CONFIG_BACKEND_CANNOT_GET_CONFIG_BASE.get(getExceptionMessage(e));
118      throw new ConfigException(message, e);
119    }
120
121
122    // If the configuration root entry is null, then assume it doesn't exist.
123    // In that case, then fail.  At least that entry must exist in the
124    // configuration, even if there are no backends defined below it.
125    if (backendRoot == null)
126    {
127      throw new ConfigException(ERR_CONFIG_BACKEND_BASE_DOES_NOT_EXIST.get());
128    }
129    initializeBackends(backendIDsToStart, root);
130  }
131
132  /**
133   * Initializes specified backends. If a backend has been already initialized, do nothing.
134   * This should only be called at Directory Server startup, after #initializeBackendConfig()
135   *
136   * @param backendIDsToStart
137   *           The list of backendID to start. Everything will be started if empty.
138   * @param root
139   *           The configuration of the server's Root backend
140   * @throws ConfigException
141   *           If a critical configuration problem prevents the backend
142   *           initialization from succeeding.
143   */
144  public void initializeBackends(Collection<String> backendIDsToStart, RootCfg root) throws ConfigException
145  {
146    // Initialize existing backends.
147    for (String name : root.listBackends())
148    {
149      // Get the handler's configuration.
150      // This will decode and validate its properties.
151      final BackendCfg backendCfg = root.getBackend(name);
152      final String backendID = backendCfg.getBackendId();
153      if (!backendIDsToStart.isEmpty() && !backendIDsToStart.contains(backendID))
154      {
155        continue;
156      }
157      if (DirectoryServer.hasBackend(backendID))
158      {
159        // Skip this backend if it is already initialized and registered as available.
160        continue;
161      }
162
163      // Register as a change listener for this backend so that we can be
164      // notified when it is disabled or enabled.
165      backendCfg.addChangeListener(this);
166
167      final DN backendDN = backendCfg.dn();
168      if (!backendCfg.isEnabled())
169      {
170        logger.debug(INFO_CONFIG_BACKEND_DISABLED, backendDN);
171        continue;
172      }
173
174      // See if the entry contains an attribute that specifies the class name
175      // for the backend implementation.  If it does, then load it and make
176      // sure that it's a valid backend implementation.  There is no such
177      // attribute, the specified class cannot be loaded, or it does not
178      // contain a valid backend implementation, then log an error and skip it.
179      String className = backendCfg.getJavaClass();
180
181      Backend<? extends BackendCfg> backend;
182      try
183      {
184        backend = loadBackendClass(className).newInstance();
185      }
186      catch (Exception e)
187      {
188        logger.traceException(e);
189        logger.error(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE, className, backendDN, stackTraceToSingleLineString(e));
190        continue;
191      }
192
193      initializeBackend(backend, backendCfg);
194    }
195  }
196
197  private void initializeConfigurationBackend() throws InitializationException
198  {
199    final ConfigurationBackend configBackend =
200        new ConfigurationBackend(serverContext, DirectoryServer.getConfigurationHandler());
201    initializeBackend(configBackend, configBackend.getBackendCfg());
202  }
203
204  private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg)
205  {
206    ConfigChangeResult ccr = new ConfigChangeResult();
207    initializeBackend(backend, backendCfg, ccr);
208    for (LocalizableMessage msg : ccr.getMessages())
209    {
210      logger.error(msg);
211    }
212  }
213
214  private void initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr)
215  {
216    backend.setBackendID(backendCfg.getBackendId());
217    backend.setWritabilityMode(toWritabilityMode(backendCfg.getWritabilityMode()));
218
219    if (acquireSharedLock(backend, backendCfg.getBackendId(), ccr) && configureAndOpenBackend(backend, backendCfg, ccr))
220    {
221      registerBackend(backend, backendCfg, ccr);
222    }
223  }
224
225  /**
226   * Acquire a shared lock on this backend. This will prevent operations like LDIF import or restore
227   * from occurring while the backend is active.
228   */
229  private boolean acquireSharedLock(Backend<?> backend, String backendID, final ConfigChangeResult ccr)
230  {
231    try
232    {
233      String lockFile = LockFileManager.getBackendLockFileName(backend);
234      StringBuilder failureReason = new StringBuilder();
235      if (!LockFileManager.acquireSharedLock(lockFile, failureReason))
236      {
237        cannotAcquireLock(backendID, ccr, failureReason);
238        return false;
239      }
240      return true;
241    }
242    catch (Exception e)
243    {
244      logger.traceException(e);
245
246      cannotAcquireLock(backendID, ccr, stackTraceToSingleLineString(e));
247      return false;
248    }
249  }
250
251  private void cannotAcquireLock(String backendID, final ConfigChangeResult ccr, CharSequence failureReason)
252  {
253    LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, failureReason);
254    logger.error(message);
255
256    // FIXME -- Do we need to send an admin alert?
257    ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
258    ccr.setAdminActionRequired(true);
259    ccr.addMessage(message);
260  }
261
262  private void releaseSharedLock(Backend<?> backend, String backendID)
263  {
264    try
265    {
266      String lockFile = LockFileManager.getBackendLockFileName(backend);
267      StringBuilder failureReason = new StringBuilder();
268      if (! LockFileManager.releaseLock(lockFile, failureReason))
269      {
270        logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, failureReason);
271        // FIXME -- Do we need to send an admin alert?
272      }
273    }
274    catch (Exception e2)
275    {
276      logger.traceException(e2);
277
278      logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, stackTraceToSingleLineString(e2));
279      // FIXME -- Do we need to send an admin alert?
280    }
281  }
282
283  @Override
284  public boolean isConfigurationChangeAcceptable(
285       BackendCfg configEntry,
286       List<LocalizableMessage> unacceptableReason)
287  {
288    DN backendDN = configEntry.dn();
289
290
291    Set<DN> baseDNs = configEntry.getBaseDN();
292
293    // See if the backend is registered with the server.  If it is, then
294    // see what's changed and whether those changes are acceptable.
295    Backend<?> backend = registeredBackends.get(backendDN);
296    if (backend != null)
297    {
298      LinkedHashSet<DN> removedDNs = new LinkedHashSet<>(backend.getBaseDNs());
299      LinkedHashSet<DN> addedDNs = new LinkedHashSet<>(baseDNs);
300      Iterator<DN> iterator = removedDNs.iterator();
301      while (iterator.hasNext())
302      {
303        DN dn = iterator.next();
304        if (addedDNs.remove(dn))
305        {
306          iterator.remove();
307        }
308      }
309
310      // Copy the directory server's base DN registry and make the
311      // requested changes to see if it complains.
312      BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
313      for (DN dn : removedDNs)
314      {
315        try
316        {
317          reg.deregisterBaseDN(dn);
318        }
319        catch (DirectoryException de)
320        {
321          logger.traceException(de);
322
323          unacceptableReason.add(de.getMessageObject());
324          return false;
325        }
326      }
327
328      for (DN dn : addedDNs)
329      {
330        try
331        {
332          reg.registerBaseDN(dn, backend, false);
333        }
334        catch (DirectoryException de)
335        {
336          logger.traceException(de);
337
338          unacceptableReason.add(de.getMessageObject());
339          return false;
340        }
341      }
342    }
343    else if (configEntry.isEnabled())
344    {
345      /*
346       * If the backend was not enabled, it has not been registered with directory server, so
347       * no listeners will be registered at the lower layers. Verify as it was an add.
348       */
349      String className = configEntry.getJavaClass();
350      try
351      {
352        Class<Backend<BackendCfg>> backendClass = loadBackendClass(className);
353        if (! Backend.class.isAssignableFrom(backendClass))
354        {
355          unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
356          return false;
357        }
358
359        Backend<BackendCfg> b = backendClass.newInstance();
360        if (! b.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext))
361        {
362          return false;
363        }
364      }
365      catch (Exception e)
366      {
367        logger.traceException(e);
368        unacceptableReason.add(
369            ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(className, backendDN, stackTraceToSingleLineString(e)));
370        return false;
371      }
372    }
373
374    // If we've gotten to this point, then it is acceptable as far as we are
375    // concerned.  If it is unacceptable according to the configuration for that
376    // backend, then the backend itself will need to make that determination.
377    return true;
378  }
379
380  @Override
381  public ConfigChangeResult applyConfigurationChange(BackendCfg cfg)
382  {
383    DN backendDN = cfg.dn();
384    Backend<? extends BackendCfg> backend = registeredBackends.get(backendDN);
385    final ConfigChangeResult ccr = new ConfigChangeResult();
386
387    // See if the entry contains an attribute that indicates whether the
388    // backend should be enabled.
389    boolean needToEnable = false;
390    try
391    {
392      if (cfg.isEnabled())
393      {
394        // The backend is marked as enabled.  See if that is already true.
395        if (backend == null)
396        {
397          needToEnable = true;
398        } // else already enabled, no need to do anything.
399      }
400      else
401      {
402        // The backend is marked as disabled.  See if that is already true.
403        if (backend != null)
404        {
405          // It isn't disabled, so we will do so now and deregister it from the
406          // Directory Server.
407          deregisterBackend(backendDN, backend);
408
409          backend.finalizeBackend();
410
411          releaseSharedLock(backend, backend.getBackendID());
412
413          return ccr;
414        } // else already disabled, no need to do anything.
415      }
416    }
417    catch (Exception e)
418    {
419      logger.traceException(e);
420
421      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
422      ccr.addMessage(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(backendDN,
423          stackTraceToSingleLineString(e)));
424      return ccr;
425    }
426
427    // See if the entry contains an attribute that specifies the class name
428    // for the backend implementation.  If it does, then load it and make sure
429    // that it's a valid backend implementation.  There is no such attribute,
430    // the specified class cannot be loaded, or it does not contain a valid
431    // backend implementation, then log an error and skip it.
432    String className = cfg.getJavaClass();
433
434    // See if this backend is currently active and if so if the name of the class is the same.
435    if (backend != null && !className.equals(backend.getClass().getName()))
436    {
437      // It is not the same. Try to load it and see if it is a valid backend implementation.
438      try
439      {
440        Class<?> backendClass = DirectoryServer.loadClass(className);
441        if (Backend.class.isAssignableFrom(backendClass))
442        {
443          // It appears to be a valid backend class.  We'll return that the
444          // change is successful, but indicate that some administrative
445          // action is required.
446          ccr.addMessage(NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get(
447              backendDN, backend.getClass().getName(), className));
448          ccr.setAdminActionRequired(true);
449        }
450        else
451        {
452          // It is not a valid backend class.  This is an error.
453          ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
454          ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
455        }
456        return ccr;
457      }
458      catch (Exception e)
459      {
460        logger.traceException(e);
461
462        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
463        ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
464                className, backendDN, stackTraceToSingleLineString(e)));
465        return ccr;
466      }
467    }
468
469
470    // If we've gotten here, then that should mean that we need to enable the
471    // backend.  Try to do so.
472    if (needToEnable)
473    {
474      try
475      {
476        backend = loadBackendClass(className).newInstance();
477      }
478      catch (Exception e)
479      {
480        // It is not a valid backend class.  This is an error.
481        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
482        ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
483        return ccr;
484      }
485
486      initializeBackend(backend, cfg, ccr);
487      return ccr;
488    }
489    else if (ccr.getResultCode() == ResultCode.SUCCESS && backend != null)
490    {
491      backend.setWritabilityMode(toWritabilityMode(cfg.getWritabilityMode()));
492    }
493
494    return ccr;
495  }
496
497  private boolean registerBackend(Backend<? extends BackendCfg> backend, BackendCfg backendCfg, ConfigChangeResult ccr)
498  {
499    for (BackendInitializationListener listener : getBackendInitializationListeners())
500    {
501      listener.performBackendPreInitializationProcessing(backend);
502    }
503
504    try
505    {
506      DirectoryServer.registerBackend(backend);
507    }
508    catch (Exception e)
509    {
510      logger.traceException(e);
511
512      LocalizableMessage message =
513          WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(backendCfg.getBackendId(), getExceptionMessage(e));
514      logger.error(message);
515
516      // FIXME -- Do we need to send an admin alert?
517      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
518      ccr.addMessage(message);
519      return false;
520    }
521
522    for (BackendInitializationListener listener : getBackendInitializationListeners())
523    {
524      listener.performBackendPostInitializationProcessing(backend);
525    }
526
527    registeredBackends.put(backendCfg.dn(), backend);
528    return true;
529  }
530
531  @Override
532  public boolean isConfigurationAddAcceptable(
533       BackendCfg configEntry,
534       List<LocalizableMessage> unacceptableReason)
535  {
536    DN backendDN = configEntry.dn();
537
538
539    // See if the entry contains an attribute that specifies the backend ID.  If
540    // it does not, then skip it.
541    String backendID = configEntry.getBackendId();
542    if (DirectoryServer.hasBackend(backendID))
543    {
544      unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID));
545      return false;
546    }
547
548
549    // See if the entry contains an attribute that specifies the set of base DNs
550    // for the backend.  If it does not, then skip it.
551    Set<DN> baseList = configEntry.getBaseDN();
552    DN[] baseDNs = new DN[baseList.size()];
553    baseList.toArray(baseDNs);
554
555
556    // See if the entry contains an attribute that specifies the class name
557    // for the backend implementation.  If it does, then load it and make sure
558    // that it's a valid backend implementation.  There is no such attribute,
559    // the specified class cannot be loaded, or it does not contain a valid
560    // backend implementation, then log an error and skip it.
561    String className = configEntry.getJavaClass();
562
563    Backend<BackendCfg> backend;
564    try
565    {
566      backend = loadBackendClass(className).newInstance();
567    }
568    catch (Exception e)
569    {
570      logger.traceException(e);
571
572      unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
573              className, backendDN, stackTraceToSingleLineString(e)));
574      return false;
575    }
576
577
578    // Make sure that all of the base DNs are acceptable for use in the server.
579    BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
580    for (DN baseDN : baseDNs)
581    {
582      if (baseDN.isRootDN())
583      {
584        unacceptableReason.add(ERR_CONFIG_BACKEND_BASE_IS_EMPTY.get(backendDN));
585        return false;
586      }
587      try
588      {
589        reg.registerBaseDN(baseDN, backend, false);
590      }
591      catch (DirectoryException de)
592      {
593        unacceptableReason.add(de.getMessageObject());
594        return false;
595      }
596      catch (Exception e)
597      {
598        unacceptableReason.add(getExceptionMessage(e));
599        return false;
600      }
601    }
602
603    return backend.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext);
604  }
605
606  @Override
607  public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg)
608  {
609    DN                backendDN           = cfg.dn();
610    final ConfigChangeResult ccr = new ConfigChangeResult();
611
612    // Register as a change listener for this backend entry so that we will
613    // be notified of any changes that may be made to it.
614    cfg.addChangeListener(this);
615
616    // See if the entry contains an attribute that indicates whether the backend should be enabled.
617    // If it does not, or if it is not set to "true", then skip it.
618    if (!cfg.isEnabled())
619    {
620      // The backend is explicitly disabled.  We will log a message to
621      // indicate that it won't be enabled and return.
622      LocalizableMessage message = INFO_CONFIG_BACKEND_DISABLED.get(backendDN);
623      logger.debug(message);
624      ccr.addMessage(message);
625      return ccr;
626    }
627
628
629
630    // See if the entry contains an attribute that specifies the backend ID.  If
631    // it does not, then skip it.
632    String backendID = cfg.getBackendId();
633    if (DirectoryServer.hasBackend(backendID))
634    {
635      LocalizableMessage message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID);
636      logger.warn(message);
637      ccr.addMessage(message);
638      return ccr;
639    }
640
641
642    // See if the entry contains an attribute that specifies the class name
643    // for the backend implementation.  If it does, then load it and make sure
644    // that it's a valid backend implementation.  There is no such attribute,
645    // the specified class cannot be loaded, or it does not contain a valid
646    // backend implementation, then log an error and skip it.
647    String className = cfg.getJavaClass();
648
649    Backend<? extends BackendCfg> backend;
650    try
651    {
652      backend = loadBackendClass(className).newInstance();
653    }
654    catch (Exception e)
655    {
656      logger.traceException(e);
657
658      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
659      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
660          className, backendDN, stackTraceToSingleLineString(e)));
661      return ccr;
662    }
663
664    initializeBackend(backend, cfg, ccr);
665    return ccr;
666  }
667
668  private boolean configureAndOpenBackend(Backend<?> backend, BackendCfg cfg, ConfigChangeResult ccr)
669  {
670    try
671    {
672      configureAndOpenBackend(backend, cfg);
673      return true;
674    }
675    catch (Exception e)
676    {
677      logger.traceException(e);
678
679      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
680      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get(
681          cfg.getJavaClass(), cfg.dn(), stackTraceToSingleLineString(e)));
682
683      releaseSharedLock(backend, cfg.getBackendId());
684      return false;
685    }
686  }
687
688  @SuppressWarnings({ "unchecked", "rawtypes" })
689  private void configureAndOpenBackend(Backend backend, BackendCfg cfg) throws ConfigException, InitializationException
690  {
691    backend.configureBackend(cfg, serverContext);
692    backend.openBackend();
693  }
694
695  @SuppressWarnings("unchecked")
696  private Class<Backend<BackendCfg>> loadBackendClass(String className) throws Exception
697  {
698    return (Class<Backend<BackendCfg>>) DirectoryServer.loadClass(className);
699  }
700
701  private WritabilityMode toWritabilityMode(BackendCfgDefn.WritabilityMode writabilityMode)
702  {
703    switch (writabilityMode)
704    {
705    case DISABLED:
706      return WritabilityMode.DISABLED;
707    case ENABLED:
708      return WritabilityMode.ENABLED;
709    case INTERNAL_ONLY:
710      return WritabilityMode.INTERNAL_ONLY;
711    default:
712      return WritabilityMode.ENABLED;
713    }
714  }
715
716  @Override
717  public boolean isConfigurationDeleteAcceptable(
718       BackendCfg configEntry,
719       List<LocalizableMessage> unacceptableReason)
720  {
721    DN backendDN = configEntry.dn();
722
723
724    // See if this backend config manager has a backend registered with the
725    // provided DN.  If not, then we don't care if the entry is deleted.  If we
726    // do know about it, then that means that it is enabled and we will not
727    // allow removing a backend that is enabled.
728    Backend<?> backend = registeredBackends.get(backendDN);
729    if (backend == null)
730    {
731      return true;
732    }
733
734
735    // See if the backend has any subordinate backends.  If so, then it is not
736    // acceptable to remove it.  Otherwise, it should be fine.
737    Backend<?>[] subBackends = backend.getSubordinateBackends();
738    if (subBackends != null && subBackends.length != 0)
739    {
740      unacceptableReason.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
741      return false;
742    }
743    return true;
744  }
745
746  @Override
747  public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry)
748  {
749    DN                backendDN           = configEntry.dn();
750    final ConfigChangeResult ccr = new ConfigChangeResult();
751
752    // See if this backend config manager has a backend registered with the
753    // provided DN.  If not, then we don't care if the entry is deleted.
754    Backend<?> backend = registeredBackends.get(backendDN);
755    if (backend == null)
756    {
757      return ccr;
758    }
759
760    // See if the backend has any subordinate backends.  If so, then it is not
761    // acceptable to remove it.  Otherwise, it should be fine.
762    Backend<?>[] subBackends = backend.getSubordinateBackends();
763    if (subBackends != null && subBackends.length > 0)
764    {
765      ccr.setResultCode(UNWILLING_TO_PERFORM);
766      ccr.addMessage(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
767      return ccr;
768    }
769
770    deregisterBackend(backendDN, backend);
771
772    try
773    {
774      backend.finalizeBackend();
775    }
776    catch (Exception e)
777    {
778      logger.traceException(e);
779    }
780
781    configEntry.removeChangeListener(this);
782
783    releaseSharedLock(backend, backend.getBackendID());
784
785    return ccr;
786  }
787
788  private void deregisterBackend(DN backendDN, Backend<?> backend)
789  {
790    for (BackendInitializationListener listener : getBackendInitializationListeners())
791    {
792      listener.performBackendPreFinalizationProcessing(backend);
793    }
794
795    registeredBackends.remove(backendDN);
796    DirectoryServer.deregisterBackend(backend);
797
798    for (BackendInitializationListener listener : getBackendInitializationListeners())
799    {
800      listener.performBackendPostFinalizationProcessing(backend);
801    }
802  }
803}