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