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