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.performBackendInitializationProcessing(backend); 262 } 263 264 265 // Register the backend with the server. 266 try 267 { 268 DirectoryServer.registerBackend(backend); 269 } 270 catch (Exception e) 271 { 272 logger.traceException(e); 273 274 logger.warn(WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND, backendID, getExceptionMessage(e)); 275 // FIXME -- Do we need to send an admin alert? 276 } 277 278 279 // Put this backend in the hash so that we will be able to find it if it 280 // is altered. 281 registeredBackends.put(backendDN, backend); 282 283 } 284 else 285 { 286 // The backend is explicitly disabled. Log a mild warning and continue. 287 logger.debug(INFO_CONFIG_BACKEND_DISABLED, backendDN); 288 } 289 } 290 } 291 292 293 /** {@inheritDoc} */ 294 @Override 295 public boolean isConfigurationChangeAcceptable( 296 BackendCfg configEntry, 297 List<LocalizableMessage> unacceptableReason) 298 { 299 DN backendDN = configEntry.dn(); 300 301 302 Set<DN> baseDNs = configEntry.getBaseDN(); 303 304 // See if the backend is registered with the server. If it is, then 305 // see what's changed and whether those changes are acceptable. 306 Backend<?> backend = registeredBackends.get(backendDN); 307 if (backend != null) 308 { 309 LinkedHashSet<DN> removedDNs = newLinkedHashSet(backend.getBaseDNs()); 310 LinkedHashSet<DN> addedDNs = new LinkedHashSet<>(baseDNs); 311 Iterator<DN> iterator = removedDNs.iterator(); 312 while (iterator.hasNext()) 313 { 314 DN dn = iterator.next(); 315 if (addedDNs.remove(dn)) 316 { 317 iterator.remove(); 318 } 319 } 320 321 // Copy the directory server's base DN registry and make the 322 // requested changes to see if it complains. 323 BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry(); 324 for (DN dn : removedDNs) 325 { 326 try 327 { 328 reg.deregisterBaseDN(dn); 329 } 330 catch (DirectoryException de) 331 { 332 logger.traceException(de); 333 334 unacceptableReason.add(de.getMessageObject()); 335 return false; 336 } 337 } 338 339 for (DN dn : addedDNs) 340 { 341 try 342 { 343 reg.registerBaseDN(dn, backend, false); 344 } 345 catch (DirectoryException de) 346 { 347 logger.traceException(de); 348 349 unacceptableReason.add(de.getMessageObject()); 350 return false; 351 } 352 } 353 } 354 else if (configEntry.isEnabled()) 355 { 356 /* 357 * If the backend was not enabled, it has not been registered with directory server, so 358 * no listeners will be registered at the lower layers. Verify as it was an add. 359 */ 360 String className = configEntry.getJavaClass(); 361 try 362 { 363 Class<Backend<BackendCfg>> backendClass = loadBackendClass(className); 364 if (! Backend.class.isAssignableFrom(backendClass)) 365 { 366 unacceptableReason.add(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); 367 return false; 368 } 369 370 Backend<BackendCfg> b = backendClass.newInstance(); 371 if (! b.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext)) 372 { 373 return false; 374 } 375 } 376 catch (Exception e) 377 { 378 logger.traceException(e); 379 unacceptableReason.add( 380 ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get(className, backendDN, stackTraceToSingleLineString(e))); 381 return false; 382 } 383 } 384 385 // If we've gotten to this point, then it is acceptable as far as we are 386 // concerned. If it is unacceptable according to the configuration for that 387 // backend, then the backend itself will need to make that determination. 388 return true; 389 } 390 391 392 /** {@inheritDoc} */ 393 @Override 394 public ConfigChangeResult applyConfigurationChange(BackendCfg cfg) 395 { 396 DN backendDN = cfg.dn(); 397 Backend<? extends BackendCfg> backend = registeredBackends.get(backendDN); 398 final ConfigChangeResult ccr = new ConfigChangeResult(); 399 400 // See if the entry contains an attribute that indicates whether the 401 // backend should be enabled. 402 boolean needToEnable = false; 403 try 404 { 405 if (cfg.isEnabled()) 406 { 407 // The backend is marked as enabled. See if that is already true. 408 if (backend == null) 409 { 410 needToEnable = true; 411 } // else already enabled, no need to do anything. 412 } 413 else 414 { 415 // The backend is marked as disabled. See if that is already true. 416 if (backend != null) 417 { 418 // It isn't disabled, so we will do so now and deregister it from the 419 // Directory Server. 420 registeredBackends.remove(backendDN); 421 DirectoryServer.deregisterBackend(backend); 422 423 for (BackendInitializationListener listener : getBackendInitializationListeners()) 424 { 425 listener.performBackendFinalizationProcessing(backend); 426 } 427 428 backend.finalizeBackend(); 429 430 // Remove the shared lock for this backend. 431 try 432 { 433 String lockFile = LockFileManager.getBackendLockFileName(backend); 434 StringBuilder failureReason = new StringBuilder(); 435 if (! LockFileManager.releaseLock(lockFile, failureReason)) 436 { 437 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend.getBackendID(), failureReason); 438 // FIXME -- Do we need to send an admin alert? 439 } 440 } 441 catch (Exception e2) 442 { 443 logger.traceException(e2); 444 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend 445 .getBackendID(), stackTraceToSingleLineString(e2)); 446 // FIXME -- Do we need to send an admin alert? 447 } 448 449 return ccr; 450 } // else already disabled, no need to do anything. 451 } 452 } 453 catch (Exception e) 454 { 455 logger.traceException(e); 456 457 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 458 ccr.addMessage(ERR_CONFIG_BACKEND_UNABLE_TO_DETERMINE_ENABLED_STATE.get(backendDN, 459 stackTraceToSingleLineString(e))); 460 return ccr; 461 } 462 463 464 String backendID = cfg.getBackendId(); 465 WritabilityMode writabilityMode = toWritabilityMode(cfg.getWritabilityMode()); 466 467 // See if the entry contains an attribute that specifies the class name 468 // for the backend implementation. If it does, then load it and make sure 469 // that it's a valid backend implementation. There is no such attribute, 470 // the specified class cannot be loaded, or it does not contain a valid 471 // backend implementation, then log an error and skip it. 472 String className = cfg.getJavaClass(); 473 474 475 // See if this backend is currently active and if so if the name of the 476 // class is the same. 477 if (backend != null && !className.equals(backend.getClass().getName())) 478 { 479 // It is not the same. Try to load it and see if it is a valid backend 480 // implementation. 481 try 482 { 483 Class<?> backendClass = DirectoryServer.loadClass(className); 484 if (Backend.class.isAssignableFrom(backendClass)) 485 { 486 // It appears to be a valid backend class. We'll return that the 487 // change is successful, but indicate that some administrative 488 // action is required. 489 ccr.addMessage(NOTE_CONFIG_BACKEND_ACTION_REQUIRED_TO_CHANGE_CLASS.get( 490 backendDN, backend.getClass().getName(), className)); 491 ccr.setAdminActionRequired(true); 492 } 493 else 494 { 495 // It is not a valid backend class. This is an error. 496 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 497 ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); 498 } 499 return ccr; 500 } 501 catch (Exception e) 502 { 503 logger.traceException(e); 504 505 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 506 ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( 507 className, backendDN, stackTraceToSingleLineString(e))); 508 return ccr; 509 } 510 } 511 512 513 // If we've gotten here, then that should mean that we need to enable the 514 // backend. Try to do so. 515 if (needToEnable) 516 { 517 try 518 { 519 backend = loadBackendClass(className).newInstance(); 520 } 521 catch (Exception e) 522 { 523 // It is not a valid backend class. This is an error. 524 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 525 ccr.addMessage(ERR_CONFIG_BACKEND_CLASS_NOT_BACKEND.get(className, backendDN)); 526 return ccr; 527 } 528 529 530 // Set the backend ID and writability mode for this backend. 531 backend.setBackendID(backendID); 532 backend.setWritabilityMode(writabilityMode); 533 534 535 // Acquire a shared lock on this backend. This will prevent operations 536 // like LDIF import or restore from occurring while the backend is active. 537 try 538 { 539 String lockFile = LockFileManager.getBackendLockFileName(backend); 540 StringBuilder failureReason = new StringBuilder(); 541 if (! LockFileManager.acquireSharedLock(lockFile, failureReason)) 542 { 543 LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID,failureReason); 544 logger.error(message); 545 // FIXME -- Do we need to send an admin alert? 546 547 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 548 ccr.setAdminActionRequired(true); 549 ccr.addMessage(message); 550 return ccr; 551 } 552 } 553 catch (Exception e) 554 { 555 logger.traceException(e); 556 557 LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, 558 stackTraceToSingleLineString(e)); 559 logger.error(message); 560 // FIXME -- Do we need to send an admin alert? 561 562 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 563 ccr.setAdminActionRequired(true); 564 ccr.addMessage(message); 565 return ccr; 566 } 567 568 if (!initializeBackend(backend, cfg, ccr)) 569 { 570 return ccr; 571 } 572 573 for (BackendInitializationListener listener : getBackendInitializationListeners()) 574 { 575 listener.performBackendInitializationProcessing(backend); 576 } 577 578 579 // Register the backend with the server. 580 try 581 { 582 DirectoryServer.registerBackend(backend); 583 } 584 catch (Exception e) 585 { 586 logger.traceException(e); 587 588 LocalizableMessage message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get( 589 backendID, getExceptionMessage(e)); 590 logger.warn(message); 591 592 // FIXME -- Do we need to send an admin alert? 593 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 594 ccr.addMessage(message); 595 return ccr; 596 } 597 598 599 registeredBackends.put(backendDN, backend); 600 } 601 else if (ccr.getResultCode() == ResultCode.SUCCESS && backend != null) 602 { 603 backend.setWritabilityMode(writabilityMode); 604 } 605 606 return ccr; 607 } 608 609 610 /** {@inheritDoc} */ 611 @Override 612 public boolean isConfigurationAddAcceptable( 613 BackendCfg configEntry, 614 List<LocalizableMessage> unacceptableReason) 615 { 616 DN backendDN = configEntry.dn(); 617 618 619 // See if the entry contains an attribute that specifies the backend ID. If 620 // it does not, then skip it. 621 String backendID = configEntry.getBackendId(); 622 if (DirectoryServer.hasBackend(backendID)) 623 { 624 unacceptableReason.add(WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID)); 625 return false; 626 } 627 628 629 // See if the entry contains an attribute that specifies the set of base DNs 630 // for the backend. If it does not, then skip it. 631 Set<DN> baseList = configEntry.getBaseDN(); 632 DN[] baseDNs = new DN[baseList.size()]; 633 baseList.toArray(baseDNs); 634 635 636 // See if the entry contains an attribute that specifies the class name 637 // for the backend implementation. If it does, then load it and make sure 638 // that it's a valid backend implementation. There is no such attribute, 639 // the specified class cannot be loaded, or it does not contain a valid 640 // backend implementation, then log an error and skip it. 641 String className = configEntry.getJavaClass(); 642 643 Backend<BackendCfg> backend; 644 try 645 { 646 backend = loadBackendClass(className).newInstance(); 647 } 648 catch (Exception e) 649 { 650 logger.traceException(e); 651 652 unacceptableReason.add(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( 653 className, backendDN, stackTraceToSingleLineString(e))); 654 return false; 655 } 656 657 658 // Make sure that all of the base DNs are acceptable for use in the server. 659 BaseDnRegistry reg = DirectoryServer.copyBaseDnRegistry(); 660 for (DN baseDN : baseDNs) 661 { 662 try 663 { 664 reg.registerBaseDN(baseDN, backend, false); 665 } 666 catch (DirectoryException de) 667 { 668 unacceptableReason.add(de.getMessageObject()); 669 return false; 670 } 671 catch (Exception e) 672 { 673 unacceptableReason.add(getExceptionMessage(e)); 674 return false; 675 } 676 } 677 678 return backend.isConfigurationAcceptable(configEntry, unacceptableReason, serverContext); 679 } 680 681 682 683 /** {@inheritDoc} */ 684 @Override 685 public ConfigChangeResult applyConfigurationAdd(BackendCfg cfg) 686 { 687 DN backendDN = cfg.dn(); 688 final ConfigChangeResult ccr = new ConfigChangeResult(); 689 690 691 // Register as a change listener for this backend entry so that we will 692 // be notified of any changes that may be made to it. 693 cfg.addChangeListener(this); 694 695 696 // See if the entry contains an attribute that indicates whether the backend should be enabled. 697 // If it does not, or if it is not set to "true", then skip it. 698 if (!cfg.isEnabled()) 699 { 700 // The backend is explicitly disabled. We will log a message to 701 // indicate that it won't be enabled and return. 702 LocalizableMessage message = INFO_CONFIG_BACKEND_DISABLED.get(backendDN); 703 logger.debug(message); 704 ccr.addMessage(message); 705 return ccr; 706 } 707 708 709 710 // See if the entry contains an attribute that specifies the backend ID. If 711 // it does not, then skip it. 712 String backendID = cfg.getBackendId(); 713 if (DirectoryServer.hasBackend(backendID)) 714 { 715 LocalizableMessage message = WARN_CONFIG_BACKEND_DUPLICATE_BACKEND_ID.get(backendDN, backendID); 716 logger.warn(message); 717 ccr.addMessage(message); 718 return ccr; 719 } 720 721 722 WritabilityMode writabilityMode = toWritabilityMode(cfg.getWritabilityMode()); 723 724 // See if the entry contains an attribute that specifies the class name 725 // for the backend implementation. If it does, then load it and make sure 726 // that it's a valid backend implementation. There is no such attribute, 727 // the specified class cannot be loaded, or it does not contain a valid 728 // backend implementation, then log an error and skip it. 729 String className = cfg.getJavaClass(); 730 731 Backend<? extends BackendCfg> backend; 732 try 733 { 734 backend = loadBackendClass(className).newInstance(); 735 } 736 catch (Exception e) 737 { 738 logger.traceException(e); 739 740 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 741 ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INSTANTIATE.get( 742 className, backendDN, stackTraceToSingleLineString(e))); 743 return ccr; 744 } 745 746 747 // Set the backend ID and writability mode for this backend. 748 backend.setBackendID(backendID); 749 backend.setWritabilityMode(writabilityMode); 750 751 752 // Acquire a shared lock on this backend. This will prevent operations 753 // like LDIF import or restore from occurring while the backend is active. 754 try 755 { 756 String lockFile = LockFileManager.getBackendLockFileName(backend); 757 StringBuilder failureReason = new StringBuilder(); 758 if (! LockFileManager.acquireSharedLock(lockFile, failureReason)) 759 { 760 LocalizableMessage message = 761 ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get(backendID, failureReason); 762 logger.error(message); 763 // FIXME -- Do we need to send an admin alert? 764 765 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 766 ccr.setAdminActionRequired(true); 767 ccr.addMessage(message); 768 return ccr; 769 } 770 } 771 catch (Exception e) 772 { 773 logger.traceException(e); 774 775 LocalizableMessage message = ERR_CONFIG_BACKEND_CANNOT_ACQUIRE_SHARED_LOCK.get( 776 backendID, stackTraceToSingleLineString(e)); 777 logger.error(message); 778 // FIXME -- Do we need to send an admin alert? 779 780 ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION); 781 ccr.setAdminActionRequired(true); 782 ccr.addMessage(message); 783 return ccr; 784 } 785 786 787 // Perform the necessary initialization for the backend entry. 788 if (!initializeBackend(backend, cfg, ccr)) 789 { 790 return ccr; 791 } 792 793 for (BackendInitializationListener listener : getBackendInitializationListeners()) 794 { 795 listener.performBackendInitializationProcessing(backend); 796 } 797 798 // At this point, the backend should be online. Add it as one of the 799 // registered backends for this backend config manager. 800 try 801 { 802 DirectoryServer.registerBackend(backend); 803 } 804 catch (Exception e) 805 { 806 logger.traceException(e); 807 808 LocalizableMessage message = WARN_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND.get( 809 backendID, getExceptionMessage(e)); 810 logger.error(message); 811 812 // FIXME -- Do we need to send an admin alert? 813 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 814 ccr.addMessage(message); 815 return ccr; 816 } 817 818 registeredBackends.put(backendDN, backend); 819 return ccr; 820 } 821 822 private boolean initializeBackend(Backend<? extends BackendCfg> backend, BackendCfg cfg, ConfigChangeResult ccr) 823 { 824 try 825 { 826 initializeBackend(backend, cfg); 827 } 828 catch (Exception e) 829 { 830 logger.traceException(e); 831 832 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 833 ccr.addMessage(ERR_CONFIG_BACKEND_CANNOT_INITIALIZE.get( 834 cfg.getJavaClass(), cfg.dn(), stackTraceToSingleLineString(e))); 835 836 String backendID = cfg.getBackendId(); 837 try 838 { 839 String lockFile = LockFileManager.getBackendLockFileName(backend); 840 StringBuilder failureReason = new StringBuilder(); 841 if (! LockFileManager.releaseLock(lockFile, failureReason)) 842 { 843 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, failureReason); 844 // FIXME -- Do we need to send an admin alert? 845 } 846 } 847 catch (Exception e2) 848 { 849 logger.traceException(e2); 850 851 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backendID, stackTraceToSingleLineString(e2)); 852 // FIXME -- Do we need to send an admin alert? 853 } 854 855 return false; 856 } 857 return true; 858 } 859 860 @SuppressWarnings("unchecked") 861 private Class<Backend<BackendCfg>> loadBackendClass(String className) throws Exception 862 { 863 return (Class<Backend<BackendCfg>>) DirectoryServer.loadClass(className); 864 } 865 866 private WritabilityMode toWritabilityMode(BackendCfgDefn.WritabilityMode writabilityMode) 867 { 868 switch (writabilityMode) 869 { 870 case DISABLED: 871 return WritabilityMode.DISABLED; 872 case ENABLED: 873 return WritabilityMode.ENABLED; 874 case INTERNAL_ONLY: 875 return WritabilityMode.INTERNAL_ONLY; 876 default: 877 return WritabilityMode.ENABLED; 878 } 879 } 880 881 882 /** {@inheritDoc} */ 883 @Override 884 public boolean isConfigurationDeleteAcceptable( 885 BackendCfg configEntry, 886 List<LocalizableMessage> unacceptableReason) 887 { 888 DN backendDN = configEntry.dn(); 889 890 891 // See if this backend config manager has a backend registered with the 892 // provided DN. If not, then we don't care if the entry is deleted. If we 893 // do know about it, then that means that it is enabled and we will not 894 // allow removing a backend that is enabled. 895 Backend<?> backend = registeredBackends.get(backendDN); 896 if (backend == null) 897 { 898 return true; 899 } 900 901 902 // See if the backend has any subordinate backends. If so, then it is not 903 // acceptable to remove it. Otherwise, it should be fine. 904 Backend<?>[] subBackends = backend.getSubordinateBackends(); 905 if (subBackends != null && subBackends.length != 0) 906 { 907 unacceptableReason.add(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN)); 908 return false; 909 } 910 return true; 911 } 912 913 914 /** {@inheritDoc} */ 915 @Override 916 public ConfigChangeResult applyConfigurationDelete(BackendCfg configEntry) 917 { 918 DN backendDN = configEntry.dn(); 919 final ConfigChangeResult ccr = new ConfigChangeResult(); 920 921 // See if this backend config manager has a backend registered with the 922 // provided DN. If not, then we don't care if the entry is deleted. 923 Backend<?> backend = registeredBackends.get(backendDN); 924 if (backend == null) 925 { 926 return ccr; 927 } 928 929 // See if the backend has any subordinate backends. If so, then it is not 930 // acceptable to remove it. Otherwise, it should be fine. 931 Backend<?>[] subBackends = backend.getSubordinateBackends(); 932 if (subBackends != null && subBackends.length > 0) 933 { 934 ccr.setResultCode(UNWILLING_TO_PERFORM); 935 ccr.addMessage(NOTE_CONFIG_BACKEND_CANNOT_REMOVE_BACKEND_WITH_SUBORDINATES.get(backendDN)); 936 return ccr; 937 } 938 939 registeredBackends.remove(backendDN); 940 941 try 942 { 943 backend.finalizeBackend(); 944 } 945 catch (Exception e) 946 { 947 logger.traceException(e); 948 } 949 950 for (BackendInitializationListener listener : getBackendInitializationListeners()) 951 { 952 listener.performBackendFinalizationProcessing(backend); 953 } 954 955 DirectoryServer.deregisterBackend(backend); 956 configEntry.removeChangeListener(this); 957 958 // Remove the shared lock for this backend. 959 try 960 { 961 String lockFile = LockFileManager.getBackendLockFileName(backend); 962 StringBuilder failureReason = new StringBuilder(); 963 if (! LockFileManager.releaseLock(lockFile, failureReason)) 964 { 965 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend.getBackendID(), failureReason); 966 // FIXME -- Do we need to send an admin alert? 967 } 968 } 969 catch (Exception e2) 970 { 971 logger.traceException(e2); 972 logger.warn(WARN_CONFIG_BACKEND_CANNOT_RELEASE_SHARED_LOCK, backend 973 .getBackendID(), stackTraceToSingleLineString(e2)); 974 // FIXME -- Do we need to send an admin alert? 975 } 976 977 return ccr; 978 } 979 980 @SuppressWarnings({ "unchecked", "rawtypes" }) 981 private void initializeBackend(Backend backend, BackendCfg cfg) 982 throws ConfigException, InitializationException 983 { 984 backend.configureBackend(cfg, serverContext); 985 backend.openBackend(); 986 } 987}