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