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.performBackendPreInitializationProcessing(backend);
262        }
263
264        // Register the backend with the server.
265        try
266        {
267          DirectoryServer.registerBackend(backend);
268        }
269        catch (Exception e)
270        {
271          logger.traceException(e);
272
273          logger.warn(WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND, backendID, getExceptionMessage(e));
274          // FIXME -- Do we need to send an admin alert?
275        }
276
277        for (BackendInitializationListener listener : getBackendInitializationListeners())
278        {
279          listener.performBackendPostInitializationProcessing(backend);
280        }
281
282        // Put this backend in the hash so that we will be able to find it if it
283        // is altered.
284        registeredBackends.put(backendDN, backend);
285
286      }
287      else
288      {
289        // The backend is explicitly disabled.  Log a mild warning and continue.
290        logger.debug(INFO_CONFIG_BACKEND_DISABLED, backendDN);
291      }
292    }
293  }
294
295
296  /** {@inheritDoc} */
297  @Override
298  public boolean isConfigurationChangeAcceptable(
299       BackendCfg configEntry,
300       List<LocalizableMessage> unacceptableReason)
301  {
302    DN backendDN = configEntry.dn();
303
304
305    Set<DN> baseDNs = configEntry.getBaseDN();
306
307    // See if the backend is registered with the server.  If it is, then
308    // see what's changed and whether those changes are acceptable.
309    Backend<?> backend = registeredBackends.get(backendDN);
310    if (backend != null)
311    {
312      LinkedHashSet<DN> removedDNs = newLinkedHashSet(backend.getBaseDNs());
313      LinkedHashSet<DN> addedDNs = new LinkedHashSet<>(baseDNs);
314      Iterator<DN> iterator = removedDNs.iterator();
315      while (iterator.hasNext())
316      {
317        DN dn = iterator.next();
318        if (addedDNs.remove(dn))
319        {
320          iterator.remove();
321        }
322      }
323
324      // Copy the directory server's base DN registry and make the
325      // requested changes to see if it complains.
326      BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
327      for (DN dn : removedDNs)
328      {
329        try
330        {
331          reg.deregisterBaseDN(dn);
332        }
333        catch (DirectoryException de)
334        {
335          logger.traceException(de);
336
337          unacceptableReason.add(de.getMessageObject());
338          return false;
339        }
340      }
341
342      for (DN dn : addedDNs)
343      {
344        try
345        {
346          reg.registerBaseDN(dn, backend, false);
347        }
348        catch (DirectoryException de)
349        {
350          logger.traceException(de);
351
352          unacceptableReason.add(de.getMessageObject());
353          return false;
354        }
355      }
356    }
357    else if (configEntry.isEnabled())
358    {
359      /*
360       * If the backend was not enabled, it has not been registered with directory server, so
361       * no listeners will be registered at the lower layers. Verify as it was an add.
362       */
363      String className = configEntry.getJavaClass();
364      try
365      {
366        Class<Backend<BackendCfg>> backendClass = loadBackendClass(className);
367        if (! Backend.class.isAssignableFrom(backendClass))
368        {
369          unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
370          return false;
371        }
372
373        Backend<BackendCfg> b = backendClass.newInstance();
374        if (! b.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext))
375        {
376          return false;
377        }
378      }
379      catch (Exception e)
380      {
381        logger.traceException(e);
382        unacceptableReason.add(
383            ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(className, backendDN, stackTraceToSingleLineString(e)));
384        return false;
385      }
386    }
387
388    // If we've gotten to this point, then it is acceptable as far as we are
389    // concerned.  If it is unacceptable according to the configuration for that
390    // backend, then the backend itself will need to make that determination.
391    return true;
392  }
393
394
395  /** {@inheritDoc} */
396  @Override
397  public ConfigChangeResult applyConfigurationChange(BackendCfg cfg)
398  {
399    DN backendDN = cfg.dn();
400    Backend<? extends BackendCfg> backend = registeredBackends.get(backendDN);
401    final ConfigChangeResult ccr = new ConfigChangeResult();
402
403    // See if the entry contains an attribute that indicates whether the
404    // backend should be enabled.
405    boolean needToEnable = false;
406    try
407    {
408      if (cfg.isEnabled())
409      {
410        // The backend is marked as enabled.  See if that is already true.
411        if (backend == null)
412        {
413          needToEnable = true;
414        } // else already enabled, no need to do anything.
415      }
416      else
417      {
418        // The backend is marked as disabled.  See if that is already true.
419        if (backend != null)
420        {
421          // It isn't disabled, so we will do so now and deregister it from the
422          // Directory Server.
423          registeredBackends.remove(backendDN);
424
425          for (BackendInitializationListener listener : getBackendInitializationListeners())
426          {
427            listener.performBackendPreFinalizationProcessing(backend);
428          }
429
430          DirectoryServer.deregisterBackend(backend);
431
432          for (BackendInitializationListener listener : getBackendInitializationListeners())
433          {
434            listener.performBackendPostFinalizationProcessing(backend);
435          }
436
437          backend.finalizeBackend();
438
439          // Remove the shared lock for this backend.
440          try
441          {
442            String lockFile = LockFileManager.getBackendLockFileName(backend);
443            StringBuilder failureReason = new StringBuilder();
444            if (! LockFileManager.releaseLock(lockFile, failureReason))
445            {
446              logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend.getBackendID(), failureReason);
447              // FIXME -- Do we need to send an admin alert?
448            }
449          }
450          catch (Exception e2)
451          {
452            logger.traceException(e2);
453            logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend
454                .getBackendID(), stackTraceToSingleLineString(e2));
455            // FIXME -- Do we need to send an admin alert?
456          }
457
458          return ccr;
459        } // else already disabled, no need to do anything.
460      }
461    }
462    catch (Exception e)
463    {
464      logger.traceException(e);
465
466      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
467      ccr.addMessage(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(backendDN,
468          stackTraceToSingleLineString(e)));
469      return ccr;
470    }
471
472
473    String backendID = cfg.getBackendId();
474    WritabilityMode writabilityMode = toWritabilityMode(cfg.getWritabilityMode());
475
476    // See if the entry contains an attribute that specifies the class name
477    // for the backend implementation.  If it does, then load it and make sure
478    // that it's a valid backend implementation.  There is no such attribute,
479    // the specified class cannot be loaded, or it does not contain a valid
480    // backend implementation, then log an error and skip it.
481    String className = cfg.getJavaClass();
482
483
484    // See if this backend is currently active and if so if the name of the
485    // class is the same.
486    if (backend != null && !className.equals(backend.getClass().getName()))
487    {
488      // It is not the same.  Try to load it and see if it is a valid backend
489      // implementation.
490      try
491      {
492        Class<?> backendClass = DirectoryServer.loadClass(className);
493        if (Backend.class.isAssignableFrom(backendClass))
494        {
495          // It appears to be a valid backend class.  We'll return that the
496          // change is successful, but indicate that some administrative
497          // action is required.
498          ccr.addMessage(NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get(
499              backendDN, backend.getClass().getName(), className));
500          ccr.setAdminActionRequired(true);
501        }
502        else
503        {
504          // It is not a valid backend class.  This is an error.
505          ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
506          ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
507        }
508        return ccr;
509      }
510      catch (Exception e)
511      {
512        logger.traceException(e);
513
514        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
515        ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
516                className, backendDN, stackTraceToSingleLineString(e)));
517        return ccr;
518      }
519    }
520
521
522    // If we've gotten here, then that should mean that we need to enable the
523    // backend.  Try to do so.
524    if (needToEnable)
525    {
526      try
527      {
528        backend = loadBackendClass(className).newInstance();
529      }
530      catch (Exception e)
531      {
532        // It is not a valid backend class.  This is an error.
533        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
534        ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN));
535        return ccr;
536      }
537
538
539      // Set the backend ID and writability mode for this backend.
540      backend.setBackendID(backendID);
541      backend.setWritabilityMode(writabilityMode);
542
543
544      // Acquire a shared lock on this backend.  This will prevent operations
545      // like LDIF import or restore from occurring while the backend is active.
546      try
547      {
548        String lockFile = LockFileManager.getBackendLockFileName(backend);
549        StringBuilder failureReason = new StringBuilder();
550        if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
551        {
552          LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID,failureReason);
553          logger.error(message);
554          // FIXME -- Do we need to send an admin alert?
555
556          ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
557          ccr.setAdminActionRequired(true);
558          ccr.addMessage(message);
559          return ccr;
560        }
561      }
562      catch (Exception e)
563      {
564        logger.traceException(e);
565
566        LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID,
567            stackTraceToSingleLineString(e));
568        logger.error(message);
569        // FIXME -- Do we need to send an admin alert?
570
571        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
572        ccr.setAdminActionRequired(true);
573        ccr.addMessage(message);
574        return ccr;
575      }
576
577      if (!initializeBackend(backend, cfg, ccr))
578      {
579        return ccr;
580      }
581
582      for (BackendInitializationListener listener : getBackendInitializationListeners())
583      {
584        listener.performBackendPreInitializationProcessing(backend);
585      }
586
587      // Register the backend with the server.
588      try
589      {
590        DirectoryServer.registerBackend(backend);
591      }
592      catch (Exception e)
593      {
594        logger.traceException(e);
595
596        LocalizableMessage message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(
597                backendID, getExceptionMessage(e));
598        logger.warn(message);
599
600        // FIXME -- Do we need to send an admin alert?
601        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
602        ccr.addMessage(message);
603        return ccr;
604      }
605
606      for (BackendInitializationListener listener : getBackendInitializationListeners())
607      {
608        listener.performBackendPostInitializationProcessing(backend);
609      }
610
611      registeredBackends.put(backendDN, backend);
612    }
613    else if (ccr.getResultCode() == ResultCode.SUCCESS && backend != null)
614    {
615      backend.setWritabilityMode(writabilityMode);
616    }
617
618    return ccr;
619  }
620
621
622  /** {@inheritDoc} */
623  @Override
624  public boolean isConfigurationAddAcceptable(
625       BackendCfg configEntry,
626       List<LocalizableMessage> unacceptableReason)
627  {
628    DN backendDN = configEntry.dn();
629
630
631    // See if the entry contains an attribute that specifies the backend ID.  If
632    // it does not, then skip it.
633    String backendID = configEntry.getBackendId();
634    if (DirectoryServer.hasBackend(backendID))
635    {
636      unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID));
637      return false;
638    }
639
640
641    // See if the entry contains an attribute that specifies the set of base DNs
642    // for the backend.  If it does not, then skip it.
643    Set<DN> baseList = configEntry.getBaseDN();
644    DN[] baseDNs = new DN[baseList.size()];
645    baseList.toArray(baseDNs);
646
647
648    // See if the entry contains an attribute that specifies the class name
649    // for the backend implementation.  If it does, then load it and make sure
650    // that it's a valid backend implementation.  There is no such attribute,
651    // the specified class cannot be loaded, or it does not contain a valid
652    // backend implementation, then log an error and skip it.
653    String className = configEntry.getJavaClass();
654
655    Backend<BackendCfg> backend;
656    try
657    {
658      backend = loadBackendClass(className).newInstance();
659    }
660    catch (Exception e)
661    {
662      logger.traceException(e);
663
664      unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
665              className, backendDN, stackTraceToSingleLineString(e)));
666      return false;
667    }
668
669
670    // Make sure that all of the base DNs are acceptable for use in the server.
671    BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
672    for (DN baseDN : baseDNs)
673    {
674      try
675      {
676        reg.registerBaseDN(baseDN, backend, false);
677      }
678      catch (DirectoryException de)
679      {
680        unacceptableReason.add(de.getMessageObject());
681        return false;
682      }
683      catch (Exception e)
684      {
685        unacceptableReason.add(getExceptionMessage(e));
686        return false;
687      }
688    }
689
690    return backend.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext);
691  }
692
693
694
695  /** {@inheritDoc} */
696  @Override
697  public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg)
698  {
699    DN                backendDN           = cfg.dn();
700    final ConfigChangeResult ccr = new ConfigChangeResult();
701
702
703    // Register as a change listener for this backend entry so that we will
704    // be notified of any changes that may be made to it.
705    cfg.addChangeListener(this);
706
707
708    // See if the entry contains an attribute that indicates whether the backend should be enabled.
709    // If it does not, or if it is not set to "true", then skip it.
710    if (!cfg.isEnabled())
711    {
712      // The backend is explicitly disabled.  We will log a message to
713      // indicate that it won't be enabled and return.
714      LocalizableMessage message = INFO_CONFIG_BACKEND_DISABLED.get(backendDN);
715      logger.debug(message);
716      ccr.addMessage(message);
717      return ccr;
718    }
719
720
721
722    // See if the entry contains an attribute that specifies the backend ID.  If
723    // it does not, then skip it.
724    String backendID = cfg.getBackendId();
725    if (DirectoryServer.hasBackend(backendID))
726    {
727      LocalizableMessage message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID);
728      logger.warn(message);
729      ccr.addMessage(message);
730      return ccr;
731    }
732
733
734    WritabilityMode writabilityMode = toWritabilityMode(cfg.getWritabilityMode());
735
736    // See if the entry contains an attribute that specifies the class name
737    // for the backend implementation.  If it does, then load it and make sure
738    // that it's a valid backend implementation.  There is no such attribute,
739    // the specified class cannot be loaded, or it does not contain a valid
740    // backend implementation, then log an error and skip it.
741    String className = cfg.getJavaClass();
742
743    Backend<? extends BackendCfg> backend;
744    try
745    {
746      backend = loadBackendClass(className).newInstance();
747    }
748    catch (Exception e)
749    {
750      logger.traceException(e);
751
752      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
753      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
754          className, backendDN, stackTraceToSingleLineString(e)));
755      return ccr;
756    }
757
758
759    // Set the backend ID and writability mode for this backend.
760    backend.setBackendID(backendID);
761    backend.setWritabilityMode(writabilityMode);
762
763
764    // Acquire a shared lock on this backend.  This will prevent operations
765    // like LDIF import or restore from occurring while the backend is active.
766    try
767    {
768      String lockFile = LockFileManager.getBackendLockFileName(backend);
769      StringBuilder failureReason = new StringBuilder();
770      if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
771      {
772        LocalizableMessage message =
773            ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, failureReason);
774        logger.error(message);
775        // FIXME -- Do we need to send an admin alert?
776
777        ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
778        ccr.setAdminActionRequired(true);
779        ccr.addMessage(message);
780        return ccr;
781      }
782    }
783    catch (Exception e)
784    {
785      logger.traceException(e);
786
787      LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
788          backendID, stackTraceToSingleLineString(e));
789      logger.error(message);
790      // FIXME -- Do we need to send an admin alert?
791
792      ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
793      ccr.setAdminActionRequired(true);
794      ccr.addMessage(message);
795      return ccr;
796    }
797
798
799    // Perform the necessary initialization for the backend entry.
800    if (!initializeBackend(backend, cfg, ccr))
801    {
802      return ccr;
803    }
804
805    for (BackendInitializationListener listener : getBackendInitializationListeners())
806    {
807      listener.performBackendPreInitializationProcessing(backend);
808    }
809
810    // At this point, the backend should be online.  Add it as one of the
811    // registered backends for this backend config manager.
812    try
813    {
814      DirectoryServer.registerBackend(backend);
815    }
816    catch (Exception e)
817    {
818      logger.traceException(e);
819
820      LocalizableMessage message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(
821              backendID, getExceptionMessage(e));
822      logger.error(message);
823
824      // FIXME -- Do we need to send an admin alert?
825      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
826      ccr.addMessage(message);
827      return ccr;
828    }
829
830    for (BackendInitializationListener listener : getBackendInitializationListeners())
831    {
832      listener.performBackendPostInitializationProcessing(backend);
833    }
834
835    registeredBackends.put(backendDN, backend);
836    return ccr;
837  }
838
839  private boolean initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg cfg, ConfigChangeResult ccr)
840  {
841    try
842    {
843      initializeBackend(backend, cfg);
844    }
845    catch (Exception e)
846    {
847      logger.traceException(e);
848
849      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
850      ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get(
851          cfg.getJavaClass(), cfg.dn(), stackTraceToSingleLineString(e)));
852
853      String backendID = cfg.getBackendId();
854      try
855      {
856        String lockFile = LockFileManager.getBackendLockFileName(backend);
857        StringBuilder failureReason = new StringBuilder();
858        if (! LockFileManager.releaseLock(lockFile, failureReason))
859        {
860          logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, failureReason);
861          // FIXME -- Do we need to send an admin alert?
862        }
863      }
864      catch (Exception e2)
865      {
866        logger.traceException(e2);
867
868        logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, stackTraceToSingleLineString(e2));
869        // FIXME -- Do we need to send an admin alert?
870      }
871
872      return false;
873    }
874    return true;
875  }
876
877  @SuppressWarnings("unchecked")
878  private Class<Backend<BackendCfg>> loadBackendClass(String className) throws Exception
879  {
880    return (Class<Backend<BackendCfg>>) DirectoryServer.loadClass(className);
881  }
882
883  private WritabilityMode toWritabilityMode(BackendCfgDefn.WritabilityMode writabilityMode)
884  {
885    switch (writabilityMode)
886    {
887    case DISABLED:
888      return WritabilityMode.DISABLED;
889    case ENABLED:
890      return WritabilityMode.ENABLED;
891    case INTERNAL_ONLY:
892      return WritabilityMode.INTERNAL_ONLY;
893    default:
894      return WritabilityMode.ENABLED;
895    }
896  }
897
898
899  /** {@inheritDoc} */
900  @Override
901  public boolean isConfigurationDeleteAcceptable(
902       BackendCfg configEntry,
903       List<LocalizableMessage> unacceptableReason)
904  {
905    DN backendDN = configEntry.dn();
906
907
908    // See if this backend config manager has a backend registered with the
909    // provided DN.  If not, then we don't care if the entry is deleted.  If we
910    // do know about it, then that means that it is enabled and we will not
911    // allow removing a backend that is enabled.
912    Backend<?> backend = registeredBackends.get(backendDN);
913    if (backend == null)
914    {
915      return true;
916    }
917
918
919    // See if the backend has any subordinate backends.  If so, then it is not
920    // acceptable to remove it.  Otherwise, it should be fine.
921    Backend<?>[] subBackends = backend.getSubordinateBackends();
922    if (subBackends != null && subBackends.length != 0)
923    {
924      unacceptableReason.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
925      return false;
926    }
927    return true;
928  }
929
930
931  /** {@inheritDoc} */
932  @Override
933  public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry)
934  {
935    DN                backendDN           = configEntry.dn();
936    final ConfigChangeResult ccr = new ConfigChangeResult();
937
938    // See if this backend config manager has a backend registered with the
939    // provided DN.  If not, then we don't care if the entry is deleted.
940    Backend<?> backend = registeredBackends.get(backendDN);
941    if (backend == null)
942    {
943      return ccr;
944    }
945
946    // See if the backend has any subordinate backends.  If so, then it is not
947    // acceptable to remove it.  Otherwise, it should be fine.
948    Backend<?>[] subBackends = backend.getSubordinateBackends();
949    if (subBackends != null && subBackends.length > 0)
950    {
951      ccr.setResultCode(UNWILLING_TO_PERFORM);
952      ccr.addMessage(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN));
953      return ccr;
954    }
955
956    for (BackendInitializationListener listener : getBackendInitializationListeners())
957    {
958      listener.performBackendPreFinalizationProcessing(backend);
959    }
960
961    registeredBackends.remove(backendDN);
962
963    DirectoryServer.deregisterBackend(backend);
964
965    for (BackendInitializationListener listener : getBackendInitializationListeners())
966    {
967      listener.performBackendPostFinalizationProcessing(backend);
968    }
969
970    try
971    {
972      backend.finalizeBackend();
973    }
974    catch (Exception e)
975    {
976      logger.traceException(e);
977    }
978
979    configEntry.removeChangeListener(this);
980
981    // Remove the shared lock for this backend.
982    try
983    {
984      String lockFile = LockFileManager.getBackendLockFileName(backend);
985      StringBuilder failureReason = new StringBuilder();
986      if (! LockFileManager.releaseLock(lockFile, failureReason))
987      {
988        logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend.getBackendID(), failureReason);
989        // FIXME -- Do we need to send an admin alert?
990      }
991    }
992    catch (Exception e2)
993    {
994      logger.traceException(e2);
995      logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend
996          .getBackendID(), stackTraceToSingleLineString(e2));
997      // FIXME -- Do we need to send an admin alert?
998    }
999
1000    return ccr;
1001  }
1002
1003  @SuppressWarnings({ "unchecked", "rawtypes" })
1004  private void initializeBackend(Backend backend, BackendCfg cfg)
1005       throws ConfigException, InitializationException
1006  {
1007    backend.configureBackend(cfg, serverContext);
1008    backend.openBackend();
1009  }
1010}