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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.Iterator;
034    import java.util.LinkedHashSet;
035    import java.util.List;
036    import java.util.Set;
037    import java.util.concurrent.ConcurrentHashMap;
038    
039    import org.opends.server.api.Backend;
040    import org.opends.server.api.BackendInitializationListener;
041    import org.opends.server.api.ConfigHandler;
042    import org.opends.server.config.ConfigException;
043    import org.opends.server.config.ConfigEntry;
044    import org.opends.server.config.ConfigConstants;
045    
046    import org.opends.server.types.*;
047    import static org.opends.server.loggers.ErrorLogger.logError;
048    import static org.opends.server.loggers.debug.DebugLogger.*;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import static org.opends.messages.ConfigMessages.*;
051    
052    import static org.opends.server.util.StaticUtils.*;
053    import org.opends.server.admin.server.ConfigurationChangeListener;
054    import org.opends.server.admin.server.ConfigurationAddListener;
055    import org.opends.server.admin.server.ConfigurationDeleteListener;
056    import org.opends.server.admin.server.ServerManagementContext;
057    import org.opends.server.admin.std.server.BackendCfg;
058    import org.opends.server.admin.std.server.RootCfg;
059    import org.opends.server.admin.std.meta.BackendCfgDefn;
060    
061    
062    /**
063     * This class defines a utility that will be used to manage the configuration
064     * for the set of backends defined in the Directory Server.  It will perform
065     * the necessary initialization of those backends when the server is first
066     * started, and then will manage any changes to them while the server is
067     * running.
068     */
069    public class BackendConfigManager implements
070         ConfigurationChangeListener<BackendCfg>,
071         ConfigurationAddListener<BackendCfg>,
072         ConfigurationDeleteListener<BackendCfg>
073    {
074      /**
075       * The tracer object for the debug logger.
076       */
077      private static final DebugTracer TRACER = getTracer();
078    
079    
080    
081    
082      // The mapping between configuration entry DNs and their corresponding
083      // backend implementations.
084      private ConcurrentHashMap<DN,Backend> registeredBackends;
085    
086    
087    
088      /**
089       * Creates a new instance of this backend config manager.
090       */
091      public BackendConfigManager()
092      {
093        // No implementation is required.
094      }
095    
096    
097    
098      /**
099       * Initializes the configuration associated with the Directory Server
100       * backends.  This should only be called at Directory Server startup.
101       *
102       * @throws  ConfigException  If a critical configuration problem prevents the
103       *                           backend initialization from succeeding.
104       *
105       * @throws  InitializationException  If a problem occurs while initializing
106       *                                   the backends that is not related to the
107       *                                   server configuration.
108       */
109      public void initializeBackendConfig()
110             throws ConfigException, InitializationException
111      {
112        registeredBackends = new ConcurrentHashMap<DN,Backend>();
113    
114    
115        // Create an internal server management context and retrieve
116        // the root configuration.
117        ServerManagementContext context = ServerManagementContext.getInstance();
118        RootCfg root = context.getRootConfiguration();
119    
120        // Register add and delete listeners.
121        root.addBackendAddListener(this);
122        root.addBackendDeleteListener(this);
123    
124        // Get the configuration entry that is at the root of all the backends in
125        // the server.
126        ConfigEntry backendRoot;
127        try
128        {
129          DN configEntryDN = DN.decode(ConfigConstants.DN_BACKEND_BASE);
130          backendRoot   = DirectoryServer.getConfigEntry(configEntryDN);
131        }
132        catch (Exception e)
133        {
134          if (debugEnabled())
135          {
136            TRACER.debugCaught(DebugLogLevel.ERROR, e);
137          }
138    
139          Message message =
140              ERR_CONFIG_BACKEND_CANNOT_GET_CONFIG_BASE.get(getExceptionMessage(e));
141          throw new ConfigException(message, e);
142    
143        }
144    
145    
146        // If the configuration root entry is null, then assume it doesn't exist.
147        // In that case, then fail.  At least that entry must exist in the
148        // configuration, even if there are no backends defined below it.
149        if (backendRoot == null)
150        {
151          Message message = ERR_CONFIG_BACKEND_BASE_DOES_NOT_EXIST.get();
152          throw new ConfigException(message);
153        }
154    
155    
156        // Initialize existing backends.
157        for (String name : root.listBackends())
158        {
159          // Get the handler's configuration.
160          // This will decode and validate its properties.
161          BackendCfg backendCfg = root.getBackend(name);
162    
163          DN backendDN = backendCfg.dn();
164          String backendID = backendCfg.getBackendId();
165    
166          // Register as a change listener for this backend so that we can be
167          // notified when it is disabled or enabled.
168          backendCfg.addChangeListener(this);
169    
170          // Ignore this handler if it is disabled.
171          if (backendCfg.isEnabled())
172          {
173            // If there is already a backend registered with the specified ID,
174            // then log an error and skip it.
175            if (DirectoryServer.hasBackend(backendCfg.getBackendId()))
176            {
177              Message message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(
178                  backendID, String.valueOf(backendDN));
179              logError(message);
180              continue;
181            }
182    
183            // See if the entry contains an attribute that specifies the class name
184            // for the backend implementation.  If it does, then load it and make
185            // sure that it's a valid backend implementation.  There is no such
186            // attribute, the specified class cannot be loaded, or it does not
187            // contain a valid backend implementation, then log an error and skip
188            // it.
189            String className = backendCfg.getJavaClass();
190            Class backendClass;
191    
192            Backend backend;
193            try
194            {
195              backendClass = DirectoryServer.loadClass(className);
196              backend = (Backend) backendClass.newInstance();
197            }
198            catch (Exception e)
199            {
200              if (debugEnabled())
201              {
202                TRACER.debugCaught(DebugLogLevel.ERROR, e);
203              }
204    
205              Message message = ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.
206                  get(String.valueOf(className), String.valueOf(backendDN),
207                      stackTraceToSingleLineString(e));
208              logError(message);
209              continue;
210            }
211    
212    
213            // If this backend is a configuration manager, then we don't want to do
214            // any more with it because the configuration will have already been
215            // started.
216            if (backend instanceof ConfigHandler)
217            {
218              continue;
219            }
220    
221    
222            // See if the entry contains an attribute that specifies the writability
223            // mode.
224            WritabilityMode writabilityMode = WritabilityMode.ENABLED;
225            BackendCfgDefn.WritabilityMode bwm =
226                 backendCfg.getWritabilityMode();
227            switch (bwm)
228            {
229              case DISABLED:
230                writabilityMode = WritabilityMode.DISABLED;
231                break;
232              case ENABLED:
233                writabilityMode = WritabilityMode.ENABLED;
234                break;
235              case INTERNAL_ONLY:
236                writabilityMode = WritabilityMode.INTERNAL_ONLY;
237                break;
238            }
239    
240            // Set the backend ID and writability mode for this backend.
241            backend.setBackendID(backendID);
242            backend.setWritabilityMode(writabilityMode);
243    
244    
245            // Acquire a shared lock on this backend.  This will prevent operations
246            // like LDIF import or restore from occurring while the backend is
247            // active.
248            try
249            {
250              String lockFile = LockFileManager.getBackendLockFileName(backend);
251              StringBuilder failureReason = new StringBuilder();
252              if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
253              {
254                Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
255                    backendID, String.valueOf(failureReason));
256                logError(message);
257                // FIXME -- Do we need to send an admin alert?
258                continue;
259              }
260            }
261            catch (Exception e)
262            {
263              if (debugEnabled())
264              {
265                TRACER.debugCaught(DebugLogLevel.ERROR, e);
266              }
267    
268              Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
269                  backendID, stackTraceToSingleLineString(e));
270              logError(message);
271              // FIXME -- Do we need to send an admin alert?
272              continue;
273            }
274    
275    
276            // Perform the necessary initialization for the backend entry.
277            try
278            {
279              initializeBackend(backend, backendCfg);
280            }
281            catch (Exception e)
282            {
283              if (debugEnabled())
284              {
285                TRACER.debugCaught(DebugLogLevel.ERROR, e);
286              }
287    
288              Message message = ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.
289                  get(String.valueOf(className), String.valueOf(backendDN),
290                      stackTraceToSingleLineString(e));
291              logError(message);
292    
293              try
294              {
295                String lockFile = LockFileManager.getBackendLockFileName(backend);
296                StringBuilder failureReason = new StringBuilder();
297                if (! LockFileManager.releaseLock(lockFile, failureReason))
298                {
299                  message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.
300                      get(backendID, String.valueOf(failureReason));
301                  logError(message);
302                  // FIXME -- Do we need to send an admin alert?
303                }
304              }
305              catch (Exception e2)
306              {
307                if (debugEnabled())
308                {
309                  TRACER.debugCaught(DebugLogLevel.ERROR, e2);
310                }
311    
312                message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.
313                    get(backendID, stackTraceToSingleLineString(e2));
314                logError(message);
315                // FIXME -- Do we need to send an admin alert?
316              }
317    
318              continue;
319            }
320    
321    
322            // Notify any backend initialization listeners.
323            for (BackendInitializationListener listener :
324                 DirectoryServer.getBackendInitializationListeners())
325            {
326              listener.performBackendInitializationProcessing(backend);
327            }
328    
329    
330            // Register the backend with the server.
331            try
332            {
333              DirectoryServer.registerBackend(backend);
334            }
335            catch (Exception e)
336            {
337              if (debugEnabled())
338              {
339                TRACER.debugCaught(DebugLogLevel.ERROR, e);
340              }
341    
342              Message message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(
343                  backendID, getExceptionMessage(e));
344              logError(message);
345              // FIXME -- Do we need to send an admin alert?
346            }
347    
348    
349            // Put this backend in the hash so that we will be able to find it if it
350            // is altered.
351            registeredBackends.put(backendDN, backend);
352    
353          }
354          else
355          {
356            // The backend is explicitly disabled.  Log a mild warning and
357            // continue.
358            Message message =
359                INFO_CONFIG_BACKEND_DISABLED.get(String.valueOf(backendDN));
360            logError(message);
361          }
362        }
363      }
364    
365    
366      /**
367       * {@inheritDoc}
368       */
369      public boolean isConfigurationChangeAcceptable(
370           BackendCfg configEntry,
371           List<Message> unacceptableReason)
372      {
373        DN backendDN = configEntry.dn();
374    
375    
376        Set<DN> baseDNs = configEntry.getBaseDN();
377    
378        // See if the backend is registered with the server.  If it is, then
379        // see what's changed and whether those changes are acceptable.
380        Backend backend = registeredBackends.get(backendDN);
381        if (backend != null)
382        {
383          LinkedHashSet<DN> removedDNs = new LinkedHashSet<DN>();
384          for (DN dn : backend.getBaseDNs())
385          {
386            removedDNs.add(dn);
387          }
388    
389          LinkedHashSet<DN> addedDNs = new LinkedHashSet<DN>();
390          for (DN dn : baseDNs)
391          {
392            addedDNs.add(dn);
393          }
394    
395          Iterator<DN> iterator = removedDNs.iterator();
396          while (iterator.hasNext())
397          {
398            DN dn = iterator.next();
399            if (addedDNs.remove(dn))
400            {
401              iterator.remove();
402            }
403          }
404    
405          // Copy the directory server's base DN registry and make the
406          // requested changes to see if it complains.
407          BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
408          for (DN dn : removedDNs)
409          {
410            try
411            {
412              reg.deregisterBaseDN(dn);
413            }
414            catch (DirectoryException de)
415            {
416              if (debugEnabled())
417              {
418                TRACER.debugCaught(DebugLogLevel.ERROR, de);
419              }
420    
421              unacceptableReason.add(de.getMessageObject());
422              return false;
423            }
424          }
425    
426          for (DN dn : addedDNs)
427          {
428            try
429            {
430              reg.registerBaseDN(dn, backend, false);
431            }
432            catch (DirectoryException de)
433            {
434              if (debugEnabled())
435              {
436                TRACER.debugCaught(DebugLogLevel.ERROR, de);
437              }
438    
439              unacceptableReason.add(de.getMessageObject());
440              return false;
441            }
442          }
443        }
444    
445    
446        // See if the entry contains an attribute that specifies the class name
447        // for the backend implementation.  If it does, then load it and make sure
448        // that it's a valid backend implementation.  There is no such attribute,
449        // the specified class cannot be loaded, or it does not contain a valid
450        // backend implementation, then log an error and skip it.
451        String className = configEntry.getJavaClass();
452        try
453        {
454          Class backendClass = DirectoryServer.loadClass(className);
455          if (! Backend.class.isAssignableFrom(backendClass))
456          {
457    
458            unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(
459                    String.valueOf(className), String.valueOf(backendDN)));
460            return false;
461          }
462    
463          Backend b = (Backend) backendClass.newInstance();
464          if (! b.isConfigurationAcceptable(configEntry, unacceptableReason))
465          {
466            return false;
467          }
468        }
469        catch (Exception e)
470        {
471          if (debugEnabled())
472          {
473            TRACER.debugCaught(DebugLogLevel.ERROR, e);
474          }
475    
476    
477          unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
478                  String.valueOf(className),
479                  String.valueOf(backendDN),
480                  stackTraceToSingleLineString(e)));
481          return false;
482        }
483    
484    
485        // If we've gotten to this point, then it is acceptable as far as we are
486        // concerned.  If it is unacceptable according to the configuration for that
487        // backend, then the backend itself will need to make that determination.
488        return true;
489      }
490    
491    
492      /**
493       * {@inheritDoc}
494       */
495      public ConfigChangeResult applyConfigurationChange(BackendCfg cfg)
496      {
497        DN                 backendDN           = cfg.dn();
498        Backend            backend             = registeredBackends.get(backendDN);
499        ResultCode         resultCode          = ResultCode.SUCCESS;
500        boolean            adminActionRequired = false;
501        ArrayList<Message> messages            = new ArrayList<Message>();
502    
503    
504        // See if the entry contains an attribute that indicates whether the
505        // backend should be enabled.
506        boolean needToEnable = false;
507        try
508        {
509          if (cfg.isEnabled())
510          {
511            // The backend is marked as enabled.  See if that is already true.
512            if (backend == null)
513            {
514              needToEnable = true;
515            }
516            else
517            {
518              // It's already enabled, so we don't need to do anything.
519            }
520          }
521          else
522          {
523            // The backend is marked as disabled.  See if that is already true.
524            if (backend != null)
525            {
526              // It isn't disabled, so we will do so now and deregister it from the
527              // Directory Server.
528              registeredBackends.remove(backendDN);
529              DirectoryServer.deregisterBackend(backend);
530    
531              for (BackendInitializationListener listener :
532                   DirectoryServer.getBackendInitializationListeners())
533              {
534                listener.performBackendFinalizationProcessing(backend);
535              }
536    
537              backend.finalizeBackend();
538    
539              // Remove the shared lock for this backend.
540              try
541              {
542                String lockFile = LockFileManager.getBackendLockFileName(backend);
543                StringBuilder failureReason = new StringBuilder();
544                if (! LockFileManager.releaseLock(lockFile, failureReason))
545                {
546                  Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.
547                      get(backend.getBackendID(), String.valueOf(failureReason));
548                  logError(message);
549                  // FIXME -- Do we need to send an admin alert?
550                }
551              }
552              catch (Exception e2)
553              {
554                if (debugEnabled())
555                {
556                  TRACER.debugCaught(DebugLogLevel.ERROR, e2);
557                }
558    
559                Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.
560                    get(backend.getBackendID(), stackTraceToSingleLineString(e2));
561                logError(message);
562                // FIXME -- Do we need to send an admin alert?
563              }
564    
565              return new ConfigChangeResult(resultCode, adminActionRequired,
566                                            messages);
567            }
568            else
569            {
570              // It's already disabled, so we don't need to do anything.
571            }
572          }
573        }
574        catch (Exception e)
575        {
576          if (debugEnabled())
577          {
578            TRACER.debugCaught(DebugLogLevel.ERROR, e);
579          }
580    
581    
582          messages.add(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(
583                  String.valueOf(backendDN), stackTraceToSingleLineString(e)));
584          resultCode = DirectoryServer.getServerErrorResultCode();
585          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
586        }
587    
588    
589        // See if the entry contains an attribute that specifies the backend ID for
590        // the backend.
591        String backendID = cfg.getBackendId();
592    
593        // See if the entry contains an attribute that specifies the writability
594        // mode.
595        WritabilityMode writabilityMode = WritabilityMode.ENABLED;
596        BackendCfgDefn.WritabilityMode bwm =
597             cfg.getWritabilityMode();
598        switch (bwm)
599        {
600          case DISABLED:
601            writabilityMode = WritabilityMode.DISABLED;
602            break;
603          case ENABLED:
604            writabilityMode = WritabilityMode.ENABLED;
605            break;
606          case INTERNAL_ONLY:
607            writabilityMode = WritabilityMode.INTERNAL_ONLY;
608            break;
609        }
610    
611    
612        // See if the entry contains an attribute that specifies the base DNs for
613        // the backend.
614        Set<DN> baseList = cfg.getBaseDN();
615        DN[] baseDNs = new DN[baseList.size()];
616        baseList.toArray(baseDNs);
617    
618    
619        // See if the entry contains an attribute that specifies the class name
620        // for the backend implementation.  If it does, then load it and make sure
621        // that it's a valid backend implementation.  There is no such attribute,
622        // the specified class cannot be loaded, or it does not contain a valid
623        // backend implementation, then log an error and skip it.
624        String className = cfg.getJavaClass();
625    
626    
627        // See if this backend is currently active and if so if the name of the
628        // class is the same.
629        if (backend != null)
630        {
631          if (! className.equals(backend.getClass().getName()))
632          {
633            // It is not the same.  Try to load it and see if it is a valid backend
634            // implementation.
635            try
636            {
637              Class backendClass = DirectoryServer.loadClass(className);
638              if (Backend.class.isAssignableFrom(backendClass))
639              {
640                // It appears to be a valid backend class.  We'll return that the
641                // change is successful, but indicate that some administrative
642                // action is required.
643    
644                messages.add(
645                        NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get(
646                                String.valueOf(backendDN),
647                                backend.getClass().getName(), className));
648                adminActionRequired = true;
649                return new ConfigChangeResult(resultCode, adminActionRequired,
650                                              messages);
651              }
652              else
653              {
654                // It is not a valid backend class.  This is an error.
655    
656                messages.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(
657                        String.valueOf(className), String.valueOf(backendDN)));
658                resultCode = ResultCode.CONSTRAINT_VIOLATION;
659                return new ConfigChangeResult(resultCode, adminActionRequired,
660                                              messages);
661              }
662            }
663            catch (Exception e)
664            {
665              if (debugEnabled())
666              {
667                TRACER.debugCaught(DebugLogLevel.ERROR, e);
668              }
669    
670    
671              messages.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
672                      String.valueOf(className), String.valueOf(backendDN),
673                      stackTraceToSingleLineString(e)));
674              resultCode = DirectoryServer.getServerErrorResultCode();
675              return new ConfigChangeResult(resultCode, adminActionRequired,
676                                            messages);
677            }
678          }
679        }
680    
681    
682        // If we've gotten here, then that should mean that we need to enable the
683        // backend.  Try to do so.
684        if (needToEnable)
685        {
686          Class backendClass;
687          try
688          {
689            backendClass = DirectoryServer.loadClass(className);
690            backend = (Backend) backendClass.newInstance();
691          }
692          catch (Exception e)
693          {
694            // It is not a valid backend class.  This is an error.
695    
696            messages.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(
697                    String.valueOf(className), String.valueOf(backendDN)));
698            resultCode = ResultCode.CONSTRAINT_VIOLATION;
699            return new ConfigChangeResult(resultCode, adminActionRequired,
700                                          messages);
701          }
702    
703    
704          // Set the backend ID and writability mode for this backend.
705          backend.setBackendID(backendID);
706          backend.setWritabilityMode(writabilityMode);
707    
708    
709          // Acquire a shared lock on this backend.  This will prevent operations
710          // like LDIF import or restore from occurring while the backend is active.
711          try
712          {
713            String lockFile = LockFileManager.getBackendLockFileName(backend);
714            StringBuilder failureReason = new StringBuilder();
715            if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
716            {
717              Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
718                  backendID, String.valueOf(failureReason));
719              logError(message);
720              // FIXME -- Do we need to send an admin alert?
721    
722              resultCode = ResultCode.CONSTRAINT_VIOLATION;
723              adminActionRequired = true;
724              messages.add(message);
725              return new ConfigChangeResult(resultCode, adminActionRequired,
726                                            messages);
727            }
728          }
729          catch (Exception e)
730          {
731            if (debugEnabled())
732            {
733              TRACER.debugCaught(DebugLogLevel.ERROR, e);
734            }
735    
736            Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
737                backendID, stackTraceToSingleLineString(e));
738            logError(message);
739            // FIXME -- Do we need to send an admin alert?
740    
741            resultCode = ResultCode.CONSTRAINT_VIOLATION;
742            adminActionRequired = true;
743            messages.add(message);
744            return new ConfigChangeResult(resultCode, adminActionRequired,
745                                          messages);
746          }
747    
748    
749          try
750          {
751            initializeBackend(backend, cfg);
752          }
753          catch (Exception e)
754          {
755            if (debugEnabled())
756            {
757              TRACER.debugCaught(DebugLogLevel.ERROR, e);
758            }
759    
760            messages.add(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get(
761                    String.valueOf(className), String.valueOf(backendDN),
762                    stackTraceToSingleLineString(e)));
763            resultCode = DirectoryServer.getServerErrorResultCode();
764    
765            try
766            {
767              String lockFile = LockFileManager.getBackendLockFileName(backend);
768              StringBuilder failureReason = new StringBuilder();
769              if (! LockFileManager.releaseLock(lockFile, failureReason))
770              {
771                Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.
772                    get(backendID, String.valueOf(failureReason));
773                logError(message);
774                // FIXME -- Do we need to send an admin alert?
775              }
776            }
777            catch (Exception e2)
778            {
779              if (debugEnabled())
780              {
781                TRACER.debugCaught(DebugLogLevel.ERROR, e2);
782              }
783    
784              Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.get(
785                  backendID, stackTraceToSingleLineString(e2));
786              logError(message);
787              // FIXME -- Do we need to send an admin alert?
788            }
789    
790            return new ConfigChangeResult(resultCode, adminActionRequired,
791                                          messages);
792          }
793    
794    
795          // Notify any backend initialization listeners.
796          for (BackendInitializationListener listener :
797               DirectoryServer.getBackendInitializationListeners())
798          {
799            listener.performBackendInitializationProcessing(backend);
800          }
801    
802    
803          // Register the backend with the server.
804          try
805          {
806            DirectoryServer.registerBackend(backend);
807          }
808          catch (Exception e)
809          {
810            if (debugEnabled())
811            {
812              TRACER.debugCaught(DebugLogLevel.ERROR, e);
813            }
814    
815            Message message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(
816                    backendID, getExceptionMessage(e));
817    
818            resultCode = DirectoryServer.getServerErrorResultCode();
819            messages.add(message);
820    
821            logError(message);
822            // FIXME -- Do we need to send an admin alert?
823    
824            return new ConfigChangeResult(resultCode, adminActionRequired,
825                                          messages);
826          }
827    
828    
829          registeredBackends.put(backendDN, backend);
830        }
831        else if ((resultCode == ResultCode.SUCCESS) && (backend != null))
832        {
833          // The backend is already enabled, so we may need to apply a
834          // configuration change.  Check to see if the writability mode has been
835          // changed.
836          if (writabilityMode != backend.getWritabilityMode())
837          {
838            backend.setWritabilityMode(writabilityMode);
839          }
840        }
841    
842    
843        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
844      }
845    
846    
847      /**
848       * {@inheritDoc}
849       */
850      public boolean isConfigurationAddAcceptable(
851           BackendCfg configEntry,
852           List<Message> unacceptableReason)
853      {
854        DN backendDN = configEntry.dn();
855    
856    
857        // See if the entry contains an attribute that specifies the backend ID.  If
858        // it does not, then skip it.
859        String backendID = configEntry.getBackendId();
860        if (DirectoryServer.hasBackend(backendID))
861        {
862          unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(
863                  String.valueOf(backendDN), backendID));
864          return false;
865        }
866    
867    
868        // See if the entry contains an attribute that specifies the set of base DNs
869        // for the backend.  If it does not, then skip it.
870        Set<DN> baseList = configEntry.getBaseDN();
871        DN[] baseDNs = new DN[baseList.size()];
872        baseList.toArray(baseDNs);
873    
874    
875        // See if the entry contains an attribute that specifies the class name
876        // for the backend implementation.  If it does, then load it and make sure
877        // that it's a valid backend implementation.  There is no such attribute,
878        // the specified class cannot be loaded, or it does not contain a valid
879        // backend implementation, then log an error and skip it.
880        String className = configEntry.getJavaClass();
881    
882        Backend backend;
883        try
884        {
885          Class backendClass = DirectoryServer.loadClass(className);
886          backend = (Backend) backendClass.newInstance();
887        }
888        catch (Exception e)
889        {
890          if (debugEnabled())
891          {
892            TRACER.debugCaught(DebugLogLevel.ERROR, e);
893          }
894    
895          unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
896                  String.valueOf(className),
897                  String.valueOf(backendDN),
898                  stackTraceToSingleLineString(e)));
899          return false;
900        }
901    
902    
903        // Make sure that all of the base DNs are acceptable for use in the server.
904        BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry();
905        for (DN baseDN : baseDNs)
906        {
907          try
908          {
909            reg.registerBaseDN(baseDN, backend, false);
910          }
911          catch (DirectoryException de)
912          {
913            unacceptableReason.add(de.getMessageObject());
914            return false;
915          }
916          catch (Exception e)
917          {
918            unacceptableReason.add(getExceptionMessage(e));
919            return false;
920          }
921        }
922    
923    
924        // If we've gotten to this point, then it is acceptable as far as we are
925        // concerned.  If it is unacceptable according to the configuration for that
926        // backend, then the backend itself will need to make that determination.
927        return true;
928      }
929    
930    
931    
932      /**
933       * {@inheritDoc}
934       */
935      public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg)
936      {
937        DN                backendDN           = cfg.dn();
938        ResultCode        resultCode          = ResultCode.SUCCESS;
939        boolean           adminActionRequired = false;
940        ArrayList<Message> messages            = new ArrayList<Message>();
941    
942    
943        // Register as a change listener for this backend entry so that we will
944        // be notified of any changes that may be made to it.
945        cfg.addChangeListener(this);
946    
947    
948        // See if the entry contains an attribute that indicates whether the
949        // backend should be enabled.  If it does not, or if it is not set to
950        // "true", then skip it.
951        if (!cfg.isEnabled())
952        {
953          // The backend is explicitly disabled.  We will log a message to
954          // indicate that it won't be enabled and return.
955          Message message =
956              INFO_CONFIG_BACKEND_DISABLED.get(String.valueOf(backendDN));
957          logError(message);
958          messages.add(message);
959          return new ConfigChangeResult(resultCode, adminActionRequired,
960                                        messages);
961        }
962    
963    
964    
965        // See if the entry contains an attribute that specifies the backend ID.  If
966        // it does not, then skip it.
967        String backendID = cfg.getBackendId();
968        if (DirectoryServer.hasBackend(backendID))
969        {
970          Message message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(
971              String.valueOf(backendDN), backendID);
972          logError(message);
973          messages.add(message);
974          return new ConfigChangeResult(resultCode, adminActionRequired,
975                                        messages);
976        }
977    
978    
979        // See if the entry contains an attribute that specifies the writability
980        // mode.
981        WritabilityMode writabilityMode = WritabilityMode.ENABLED;
982        BackendCfgDefn.WritabilityMode bwm =
983             cfg.getWritabilityMode();
984        switch (bwm)
985        {
986          case DISABLED:
987            writabilityMode = WritabilityMode.DISABLED;
988            break;
989          case ENABLED:
990            writabilityMode = WritabilityMode.ENABLED;
991            break;
992          case INTERNAL_ONLY:
993            writabilityMode = WritabilityMode.INTERNAL_ONLY;
994            break;
995        }
996    
997    
998        // See if the entry contains an attribute that specifies the base DNs for
999        // the entry.  If it does not, then skip it.
1000        Set<DN> dnList = cfg.getBaseDN();
1001        DN[] baseDNs = new DN[dnList.size()];
1002        dnList.toArray(baseDNs);
1003    
1004    
1005        // See if the entry contains an attribute that specifies the class name
1006        // for the backend implementation.  If it does, then load it and make sure
1007        // that it's a valid backend implementation.  There is no such attribute,
1008        // the specified class cannot be loaded, or it does not contain a valid
1009        // backend implementation, then log an error and skip it.
1010        String className = cfg.getJavaClass();
1011        Class backendClass;
1012    
1013        Backend backend;
1014        try
1015        {
1016          backendClass = DirectoryServer.loadClass(className);
1017          backend = (Backend) backendClass.newInstance();
1018        }
1019        catch (Exception e)
1020        {
1021          if (debugEnabled())
1022          {
1023            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1024          }
1025    
1026          messages.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(
1027                  String.valueOf(className),
1028                  String.valueOf(backendDN),
1029                  stackTraceToSingleLineString(e)));
1030          resultCode = DirectoryServer.getServerErrorResultCode();
1031          return new ConfigChangeResult(resultCode, adminActionRequired,
1032                                        messages);
1033        }
1034    
1035    
1036        // Set the backend ID and writability mode for this backend.
1037        backend.setBackendID(backendID);
1038        backend.setWritabilityMode(writabilityMode);
1039    
1040    
1041        // Acquire a shared lock on this backend.  This will prevent operations
1042        // like LDIF import or restore from occurring while the backend is active.
1043        try
1044        {
1045          String lockFile = LockFileManager.getBackendLockFileName(backend);
1046          StringBuilder failureReason = new StringBuilder();
1047          if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
1048          {
1049            Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
1050                backendID, String.valueOf(failureReason));
1051            logError(message);
1052            // FIXME -- Do we need to send an admin alert?
1053    
1054            resultCode = ResultCode.CONSTRAINT_VIOLATION;
1055            adminActionRequired = true;
1056            messages.add(message);
1057            return new ConfigChangeResult(resultCode, adminActionRequired,
1058                                          messages);
1059          }
1060        }
1061        catch (Exception e)
1062        {
1063          if (debugEnabled())
1064          {
1065            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1066          }
1067    
1068          Message message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(
1069              backendID, stackTraceToSingleLineString(e));
1070          logError(message);
1071          // FIXME -- Do we need to send an admin alert?
1072    
1073          resultCode = ResultCode.CONSTRAINT_VIOLATION;
1074          adminActionRequired = true;
1075          messages.add(message);
1076          return new ConfigChangeResult(resultCode, adminActionRequired,
1077                                        messages);
1078        }
1079    
1080    
1081        // Perform the necessary initialization for the backend entry.
1082        try
1083        {
1084          initializeBackend(backend, cfg);
1085        }
1086        catch (Exception e)
1087        {
1088          if (debugEnabled())
1089          {
1090            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1091          }
1092    
1093          messages.add(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get(
1094                  String.valueOf(className),
1095                  String.valueOf(backendDN),
1096                  stackTraceToSingleLineString(e)));
1097          resultCode = DirectoryServer.getServerErrorResultCode();
1098    
1099          try
1100          {
1101            String lockFile = LockFileManager.getBackendLockFileName(backend);
1102            StringBuilder failureReason = new StringBuilder();
1103            if (! LockFileManager.releaseLock(lockFile, failureReason))
1104            {
1105              Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.get(
1106                  backendID, String.valueOf(failureReason));
1107              logError(message);
1108              // FIXME -- Do we need to send an admin alert?
1109            }
1110          }
1111          catch (Exception e2)
1112          {
1113            if (debugEnabled())
1114            {
1115              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
1116            }
1117    
1118            Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.get(
1119                backendID, stackTraceToSingleLineString(e2));
1120            logError(message);
1121            // FIXME -- Do we need to send an admin alert?
1122          }
1123    
1124          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1125        }
1126    
1127    
1128        // Notify any backend initialization listeners.
1129        for (BackendInitializationListener listener :
1130             DirectoryServer.getBackendInitializationListeners())
1131        {
1132          listener.performBackendInitializationProcessing(backend);
1133        }
1134    
1135    
1136        // At this point, the backend should be online.  Add it as one of the
1137        // registered backends for this backend config manager.
1138        try
1139        {
1140          DirectoryServer.registerBackend(backend);
1141        }
1142        catch (Exception e)
1143        {
1144          if (debugEnabled())
1145          {
1146            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1147          }
1148    
1149          Message message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get(
1150                  backendID, getExceptionMessage(e));
1151    
1152          resultCode = DirectoryServer.getServerErrorResultCode();
1153          messages.add(message);
1154    
1155          logError(message);
1156          // FIXME -- Do we need to send an admin alert?
1157    
1158          return new ConfigChangeResult(resultCode, adminActionRequired,
1159                                        messages);
1160        }
1161    
1162        registeredBackends.put(backendDN, backend);
1163        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1164      }
1165    
1166    
1167      /**
1168       * {@inheritDoc}
1169       */
1170      public boolean isConfigurationDeleteAcceptable(
1171           BackendCfg configEntry,
1172           List<Message> unacceptableReason)
1173      {
1174        DN backendDN = configEntry.dn();
1175    
1176    
1177        // See if this backend config manager has a backend registered with the
1178        // provided DN.  If not, then we don't care if the entry is deleted.  If we
1179        // do know about it, then that means that it is enabled and we will not
1180        // allow removing a backend that is enabled.
1181        Backend backend = registeredBackends.get(backendDN);
1182        if (backend == null)
1183        {
1184          return true;
1185        }
1186    
1187    
1188        // See if the backend has any subordinate backends.  If so, then it is not
1189        // acceptable to remove it.  Otherwise, it should be fine.
1190        Backend[] subBackends = backend.getSubordinateBackends();
1191        if ((subBackends == null) || (subBackends.length == 0))
1192        {
1193          return true;
1194        }
1195        else
1196        {
1197          unacceptableReason.add(
1198                  NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(
1199                          String.valueOf(backendDN)));
1200          return false;
1201        }
1202      }
1203    
1204    
1205      /**
1206       * {@inheritDoc}
1207       */
1208      public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry)
1209      {
1210        DN                backendDN           = configEntry.dn();
1211        ResultCode        resultCode          = ResultCode.SUCCESS;
1212        boolean           adminActionRequired = false;
1213        ArrayList<Message> messages            = new ArrayList<Message>();
1214    
1215    
1216        // See if this backend config manager has a backend registered with the
1217        // provided DN.  If not, then we don't care if the entry is deleted.
1218        Backend backend = registeredBackends.get(backendDN);
1219        if (backend == null)
1220        {
1221          return new ConfigChangeResult(resultCode, adminActionRequired,
1222                                        messages);
1223        }
1224    
1225    
1226        // See if the backend has any subordinate backends.  If so, then it is not
1227        // acceptable to remove it.  Otherwise, it should be fine.
1228        Backend[] subBackends = backend.getSubordinateBackends();
1229        if ((subBackends == null) || (subBackends.length == 0))
1230        {
1231          registeredBackends.remove(backendDN);
1232    
1233          try
1234          {
1235            backend.finalizeBackend();
1236          }
1237          catch (Exception e)
1238          {
1239            if (debugEnabled())
1240            {
1241              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1242            }
1243          }
1244    
1245          for (BackendInitializationListener listener :
1246               DirectoryServer.getBackendInitializationListeners())
1247          {
1248            listener.performBackendFinalizationProcessing(backend);
1249          }
1250    
1251          DirectoryServer.deregisterBackend(backend);
1252          configEntry.removeChangeListener(this);
1253    
1254          // Remove the shared lock for this backend.
1255          try
1256          {
1257            String lockFile = LockFileManager.getBackendLockFileName(backend);
1258            StringBuilder failureReason = new StringBuilder();
1259            if (! LockFileManager.releaseLock(lockFile, failureReason))
1260            {
1261              Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.get(
1262                  backend.getBackendID(), String.valueOf(failureReason));
1263              logError(message);
1264              // FIXME -- Do we need to send an admin alert?
1265            }
1266          }
1267          catch (Exception e2)
1268          {
1269            if (debugEnabled())
1270            {
1271              TRACER.debugCaught(DebugLogLevel.ERROR, e2);
1272            }
1273    
1274            Message message = WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK.get(
1275                backend.getBackendID(), stackTraceToSingleLineString(e2));
1276            logError(message);
1277            // FIXME -- Do we need to send an admin alert?
1278          }
1279    
1280          return new ConfigChangeResult(resultCode, adminActionRequired,
1281                                        messages);
1282        }
1283        else
1284        {
1285    
1286          messages.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES
1287                  .get(String.valueOf(backendDN)));
1288          resultCode = ResultCode.UNWILLING_TO_PERFORM;
1289          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
1290        }
1291      }
1292    
1293      @SuppressWarnings("unchecked")
1294      private static void initializeBackend(Backend backend, BackendCfg cfg)
1295           throws ConfigException, InitializationException
1296      {
1297        backend.configureBackend(cfg);
1298        backend.initializeBackend();
1299      }
1300    
1301    }
1302