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