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