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 2013-2015 ForgeRock AS. 026 */ 027package org.opends.server.core; 028 029import java.util.*; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.i18n.slf4j.LocalizedLogger; 033import org.forgerock.opendj.ldap.ByteString; 034import org.forgerock.util.Utils; 035import org.opends.server.admin.ClassPropertyDefinition; 036import org.opends.server.admin.server.ConfigurationAddListener; 037import org.opends.server.admin.server.ConfigurationChangeListener; 038import org.opends.server.admin.server.ConfigurationDeleteListener; 039import org.opends.server.admin.server.ServerManagementContext; 040import org.opends.server.admin.std.meta.EntryCacheCfgDefn; 041import org.opends.server.admin.std.server.EntryCacheCfg; 042import org.opends.server.admin.std.server.EntryCacheMonitorProviderCfg; 043import org.opends.server.admin.std.server.RootCfg; 044import org.opends.server.api.EntryCache; 045import org.opends.server.config.ConfigConstants; 046import org.opends.server.config.ConfigEntry; 047import org.forgerock.opendj.config.server.ConfigException; 048import org.opends.server.extensions.DefaultEntryCache; 049import org.opends.server.monitors.EntryCacheMonitorProvider; 050import org.forgerock.opendj.config.server.ConfigChangeResult; 051import org.opends.server.types.DN; 052import org.opends.server.types.InitializationException; 053 054import static org.opends.messages.ConfigMessages.*; 055import static org.opends.server.util.StaticUtils.*; 056 057/** 058 * This class defines a utility that will be used to manage the configuration 059 * for the Directory Server entry cache. The default entry cache is always 060 * enabled. 061 */ 062public class EntryCacheConfigManager 063 implements 064 ConfigurationChangeListener <EntryCacheCfg>, 065 ConfigurationAddListener <EntryCacheCfg>, 066 ConfigurationDeleteListener <EntryCacheCfg> 067{ 068 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 069 070 /** The default entry cache. */ 071 private DefaultEntryCache _defaultEntryCache; 072 073 /** The entry cache order map sorted by the cache level. */ 074 @SuppressWarnings("rawtypes") 075 private SortedMap<Integer, EntryCache> cacheOrderMap = new TreeMap<>(); 076 077 /** The entry cache to level map. */ 078 private Map<DN,Integer> cacheNameToLevelMap = new HashMap<>(); 079 080 /** Global entry cache monitor provider name. */ 081 private static final String 082 DEFAULT_ENTRY_CACHE_MONITOR_PROVIDER = "Entry Caches"; 083 084 private final ServerContext serverContext; 085 086 /** 087 * Creates a new instance of this entry cache config manager. 088 * 089 * @param serverContext 090 * The server context. 091 */ 092 public EntryCacheConfigManager(ServerContext serverContext) 093 { 094 this.serverContext = serverContext; 095 } 096 097 098 /** 099 * Initializes the default entry cache. 100 * This should only be called at Directory Server startup. 101 * 102 * @throws InitializationException If a problem occurs while trying to 103 * install the default entry cache. 104 */ 105 public void initializeDefaultEntryCache() 106 throws InitializationException 107 { 108 try 109 { 110 DefaultEntryCache defaultCache = new DefaultEntryCache(); 111 defaultCache.initializeEntryCache(null); 112 DirectoryServer.setEntryCache(defaultCache); 113 _defaultEntryCache = defaultCache; 114 } 115 catch (Exception e) 116 { 117 logger.traceException(e); 118 119 LocalizableMessage message = ERR_CONFIG_ENTRYCACHE_CANNOT_INSTALL_DEFAULT_CACHE.get( 120 stackTraceToSingleLineString(e)); 121 throw new InitializationException(message, e); 122 } 123 124 } 125 126 127 /** 128 * Initializes the configuration associated with the Directory Server entry 129 * cache. This should only be called at Directory Server startup. If an 130 * error occurs, then a message will be logged for each entry cache that is 131 * failed to initialize. 132 * 133 * @throws ConfigException If a configuration problem causes the entry 134 * cache initialization process to fail. 135 */ 136 public void initializeEntryCache() 137 throws ConfigException 138 { 139 // Get the root configuration object. 140 ServerManagementContext managementContext = 141 ServerManagementContext.getInstance(); 142 RootCfg rootConfiguration = 143 managementContext.getRootConfiguration(); 144 145 // Default entry cache should be already installed with 146 // <CODE>initializeDefaultEntryCache()</CODE> method so 147 // that there will be one even if we encounter a problem later. 148 149 // Register as an add and delete listener with the root configuration so we 150 // can be notified if any entry cache entry is added or removed. 151 rootConfiguration.addEntryCacheAddListener(this); 152 rootConfiguration.addEntryCacheDeleteListener(this); 153 154 // Get the base entry cache configuration entry. 155 ConfigEntry entryCacheBase; 156 try { 157 DN configEntryDN = DN.valueOf(ConfigConstants.DN_ENTRY_CACHE_BASE); 158 entryCacheBase = DirectoryServer.getConfigEntry(configEntryDN); 159 } catch (Exception e) { 160 logger.traceException(e); 161 162 logger.warn(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY); 163 return; 164 } 165 166 // If the configuration base entry is null, then assume it doesn't exist. 167 // At least that entry must exist in the configuration, even if there are 168 // no entry cache defined below it. 169 if (entryCacheBase == null) 170 { 171 logger.error(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY); 172 return; 173 } 174 175 // Initialize every entry cache configured. 176 for (String cacheName : rootConfiguration.listEntryCaches()) 177 { 178 // Get the entry cache configuration. 179 EntryCacheCfg configuration = rootConfiguration.getEntryCache(cacheName); 180 181 // At this point, we have a configuration entry. Register a change 182 // listener with it so we can be notified of changes to it over time. 183 configuration.addChangeListener(this); 184 185 // Check if there is another entry cache installed at the same level. 186 if (!cacheOrderMap.isEmpty() 187 && cacheOrderMap.containsKey(configuration.getCacheLevel())) 188 { 189 // Log error and skip this cache. 190 logger.error(ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE, 191 configuration.dn(), configuration.getCacheLevel()); 192 continue; 193 } 194 195 // Initialize the entry cache. 196 if (configuration.isEnabled()) { 197 // Load the entry cache implementation class and install the entry 198 // cache with the server. 199 String className = configuration.getJavaClass(); 200 try { 201 loadAndInstallEntryCache(className, configuration); 202 } catch (InitializationException ie) { 203 logger.error(ie.getMessageObject()); 204 } 205 } 206 } 207 } 208 209 210 /** {@inheritDoc} */ 211 @Override 212 public boolean isConfigurationChangeAcceptable( 213 EntryCacheCfg configuration, 214 List<LocalizableMessage> unacceptableReasons 215 ) 216 { 217 // returned status -- all is fine by default 218 boolean status = true; 219 220 // Get the name of the class and make sure we can instantiate it as an 221 // entry cache. 222 String className = configuration.getJavaClass(); 223 try { 224 // Load the class but don't initialize it. 225 loadEntryCache(className, configuration, false); 226 } catch (InitializationException ie) { 227 unacceptableReasons.add(ie.getMessageObject()); 228 status = false; 229 } 230 231 if (!cacheOrderMap.isEmpty() && !cacheNameToLevelMap.isEmpty()) 232 { 233 final ByteString normDN = configuration.dn().toNormalizedByteString(); 234 if (cacheNameToLevelMap.containsKey(normDN)) { 235 int currentCacheLevel = cacheNameToLevelMap.get(normDN); 236 237 // Check if there any existing cache at the same level. 238 if (currentCacheLevel != configuration.getCacheLevel() && 239 cacheOrderMap.containsKey(configuration.getCacheLevel())) { 240 unacceptableReasons.add( 241 ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get( 242 configuration.dn(), configuration.getCacheLevel())); 243 status = false; 244 } 245 } 246 } 247 248 return status; 249 } 250 251 252 /** {@inheritDoc} */ 253 @Override 254 public ConfigChangeResult applyConfigurationChange( 255 EntryCacheCfg configuration 256 ) 257 { 258 EntryCache<? extends EntryCacheCfg> entryCache = null; 259 260 // If we this entry cache is already installed and active it 261 // should be present in the cache maps, if so use it. 262 if (!cacheOrderMap.isEmpty() && !cacheNameToLevelMap.isEmpty()) { 263 final DN dn = configuration.dn(); 264 if (cacheNameToLevelMap.containsKey(dn)) 265 { 266 int currentCacheLevel = cacheNameToLevelMap.get(dn); 267 entryCache = cacheOrderMap.get(currentCacheLevel); 268 269 // Check if the existing cache just shifted its level. 270 if (currentCacheLevel != configuration.getCacheLevel()) { 271 // Update the maps then. 272 cacheOrderMap.remove(currentCacheLevel); 273 cacheOrderMap.put(configuration.getCacheLevel(), entryCache); 274 cacheNameToLevelMap.put(dn, configuration.getCacheLevel()); 275 } 276 } 277 } 278 279 final ConfigChangeResult changeResult = new ConfigChangeResult(); 280 281 // If an entry cache was installed then remove it. 282 if (!configuration.isEnabled()) 283 { 284 configuration.getCacheLevel(); 285 if (entryCache != null) 286 { 287 EntryCacheMonitorProvider monitor = entryCache.getEntryCacheMonitor(); 288 if (monitor != null) 289 { 290 DirectoryServer.deregisterMonitorProvider(monitor); 291 monitor.finalizeMonitorProvider(); 292 entryCache.setEntryCacheMonitor(null); 293 } 294 entryCache.finalizeEntryCache(); 295 cacheOrderMap.remove(configuration.getCacheLevel()); 296 entryCache = null; 297 } 298 return changeResult; 299 } 300 301 // Push any changes made to the cache order map. 302 setCacheOrder(cacheOrderMap); 303 304 // At this point, new configuration is enabled... 305 // If the current entry cache is already enabled then we don't do 306 // anything unless the class has changed in which case we should 307 // indicate that administrative action is required. 308 String newClassName = configuration.getJavaClass(); 309 if ( entryCache != null) 310 { 311 String curClassName = entryCache.getClass().getName(); 312 boolean classIsNew = !newClassName.equals(curClassName); 313 if (classIsNew) 314 { 315 changeResult.setAdminActionRequired (true); 316 } 317 return changeResult; 318 } 319 320 // New entry cache is enabled and there were no previous one. 321 // Instantiate the new class and initialize it. 322 try 323 { 324 loadAndInstallEntryCache (newClassName, configuration); 325 } 326 catch (InitializationException ie) 327 { 328 changeResult.addMessage (ie.getMessageObject()); 329 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); 330 return changeResult; 331 } 332 333 return changeResult; 334 } 335 336 337 /** {@inheritDoc} */ 338 @Override 339 public boolean isConfigurationAddAcceptable( 340 EntryCacheCfg configuration, 341 List<LocalizableMessage> unacceptableReasons 342 ) 343 { 344 // returned status -- all is fine by default 345 // Check if there is another entry cache installed at the same level. 346 if (!cacheOrderMap.isEmpty() 347 && cacheOrderMap.containsKey(configuration.getCacheLevel())) 348 { 349 unacceptableReasons.add(ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get( 350 configuration.dn(), configuration.getCacheLevel())); 351 return false; 352 } 353 354 if (configuration.isEnabled()) 355 { 356 // Get the name of the class and make sure we can instantiate it as 357 // an entry cache. 358 String className = configuration.getJavaClass(); 359 try 360 { 361 // Load the class but don't initialize it. 362 loadEntryCache(className, configuration, false); 363 } 364 catch (InitializationException ie) 365 { 366 unacceptableReasons.add (ie.getMessageObject()); 367 return false; 368 } 369 } 370 371 return true; 372 } 373 374 375 /** {@inheritDoc} */ 376 @Override 377 public ConfigChangeResult applyConfigurationAdd(EntryCacheCfg configuration) 378 { 379 final ConfigChangeResult changeResult = new ConfigChangeResult(); 380 381 // Register a change listener with it so we can be notified of changes 382 // to it over time. 383 configuration.addChangeListener(this); 384 385 if (configuration.isEnabled()) 386 { 387 // Instantiate the class as an entry cache and initialize it. 388 String className = configuration.getJavaClass(); 389 try 390 { 391 loadAndInstallEntryCache (className, configuration); 392 } 393 catch (InitializationException ie) 394 { 395 changeResult.addMessage (ie.getMessageObject()); 396 changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); 397 return changeResult; 398 } 399 } 400 401 return changeResult; 402 } 403 404 405 /** {@inheritDoc} */ 406 @Override 407 public boolean isConfigurationDeleteAcceptable( 408 EntryCacheCfg configuration, 409 List<LocalizableMessage> unacceptableReasons 410 ) 411 { 412 // If we've gotten to this point, then it is acceptable as far as we are 413 // concerned. If it is unacceptable according to the configuration, then 414 // the entry cache itself will make that determination. 415 return true; 416 } 417 418 419 /** {@inheritDoc} */ 420 @Override 421 public ConfigChangeResult applyConfigurationDelete( 422 EntryCacheCfg configuration 423 ) 424 { 425 EntryCache<? extends EntryCacheCfg> entryCache = null; 426 427 // If we this entry cache is already installed and active it 428 // should be present in the current cache order map, use it. 429 if (!cacheOrderMap.isEmpty()) { 430 entryCache = cacheOrderMap.get(configuration.getCacheLevel()); 431 } 432 433 final ConfigChangeResult changeResult = new ConfigChangeResult(); 434 435 // If the entry cache was installed then remove it. 436 if (entryCache != null) 437 { 438 EntryCacheMonitorProvider monitor = entryCache.getEntryCacheMonitor(); 439 if (monitor != null) 440 { 441 DirectoryServer.deregisterMonitorProvider(monitor); 442 monitor.finalizeMonitorProvider(); 443 entryCache.setEntryCacheMonitor(null); 444 } 445 entryCache.finalizeEntryCache(); 446 cacheOrderMap.remove(configuration.getCacheLevel()); 447 cacheNameToLevelMap.remove(configuration.dn().toNormalizedByteString()); 448 449 // Push any changes made to the cache order map. 450 setCacheOrder(cacheOrderMap); 451 452 entryCache = null; 453 } 454 455 return changeResult; 456 } 457 458 459 /** 460 * Loads the specified class, instantiates it as an entry cache, 461 * and optionally initializes that instance. Any initialize entry 462 * cache is registered in the server. 463 * 464 * @param className The fully-qualified name of the entry cache 465 * class to load, instantiate, and initialize. 466 * @param configuration The configuration to use to initialize the 467 * entry cache, or {@code null} if the 468 * entry cache should not be initialized. 469 * 470 * @throws InitializationException If a problem occurred while attempting 471 * to initialize the entry cache. 472 */ 473 private void loadAndInstallEntryCache( 474 String className, 475 EntryCacheCfg configuration 476 ) 477 throws InitializationException 478 { 479 // Get the root configuration object. 480 ServerManagementContext managementContext = 481 ServerManagementContext.getInstance(); 482 RootCfg rootConfiguration = 483 managementContext.getRootConfiguration(); 484 485 // Load the entry cache class... 486 EntryCache<? extends EntryCacheCfg> entryCache = 487 loadEntryCache (className, configuration, true); 488 489 // ... and install the entry cache in the server. 490 491 // Add this entry cache to the current cache config maps. 492 cacheOrderMap.put(configuration.getCacheLevel(), entryCache); 493 cacheNameToLevelMap.put(configuration.dn(), configuration.getCacheLevel()); 494 495 // Push any changes made to the cache order map. 496 setCacheOrder(cacheOrderMap); 497 498 // Install and register the monitor for this cache. 499 EntryCacheMonitorProvider monitor = 500 new EntryCacheMonitorProvider(configuration.dn(). 501 rdn().getAttributeValue(0).toString(), entryCache); 502 try { 503 monitor.initializeMonitorProvider((EntryCacheMonitorProviderCfg) 504 rootConfiguration.getMonitorProvider( 505 DEFAULT_ENTRY_CACHE_MONITOR_PROVIDER)); 506 } catch (ConfigException ce) { 507 // ConfigException here means that either the entry cache monitor 508 // config entry is not present or the monitor is not enabled. In 509 // either case that means no monitor provider for this cache. 510 return; 511 } 512 entryCache.setEntryCacheMonitor(monitor); 513 DirectoryServer.registerMonitorProvider(monitor); 514 } 515 516 @SuppressWarnings({ "rawtypes", "unchecked" }) 517 private void setCacheOrder(SortedMap<Integer, EntryCache> cacheOrderMap) 518 { 519 _defaultEntryCache.setCacheOrder((SortedMap) cacheOrderMap); 520 } 521 522 /** 523 * Loads the specified class, instantiates it as an entry cache, and 524 * optionally initializes that instance. 525 * 526 * @param className The fully-qualified name of the entry cache class 527 * to load, instantiate, and initialize. 528 * @param configuration The configuration to use to initialize the entry 529 * cache. It must not be {@code null}. 530 * @param initialize Indicates whether the entry cache instance should be 531 * initialized. 532 * 533 * @return The possibly initialized entry cache. 534 * 535 * @throws InitializationException If a problem occurred while attempting 536 * to initialize the entry cache. 537 */ 538 private <T extends EntryCacheCfg> EntryCache<T> loadEntryCache( 539 String className, 540 T configuration, 541 boolean initialize 542 ) 543 throws InitializationException 544 { 545 // If we this entry cache is already installed and active it 546 // should be present in the current cache order map, use it. 547 EntryCache<T> entryCache = null; 548 if (!cacheOrderMap.isEmpty()) { 549 entryCache = cacheOrderMap.get(configuration.getCacheLevel()); 550 } 551 552 try 553 { 554 EntryCacheCfgDefn definition = EntryCacheCfgDefn.getInstance(); 555 ClassPropertyDefinition propertyDefinition = definition 556 .getJavaClassPropertyDefinition(); 557 @SuppressWarnings("unchecked") 558 Class<? extends EntryCache<T>> cacheClass = 559 (Class<? extends EntryCache<T>>) propertyDefinition 560 .loadClass(className, EntryCache.class); 561 562 // If there is some entry cache instance already initialized work with 563 // it instead of creating a new one unless explicit init is requested. 564 EntryCache<T> cache; 565 if (initialize || entryCache == null) { 566 cache = cacheClass.newInstance(); 567 } else { 568 cache = entryCache; 569 } 570 571 if (initialize) 572 { 573 cache.initializeEntryCache(configuration); 574 } 575 // This will check if configuration is acceptable on disabled 576 // and uninitialized cache instance that has no "acceptable" 577 // change listener registered to invoke and verify on its own. 578 else if (!configuration.isEnabled()) 579 { 580 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 581 if (!cache.isConfigurationAcceptable(configuration, unacceptableReasons)) 582 { 583 String buffer = Utils.joinAsString(". ", unacceptableReasons); 584 throw new InitializationException( 585 ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), buffer)); 586 } 587 } 588 589 return cache; 590 } 591 catch (Exception e) 592 { 593 logger.traceException(e); 594 595 if (!initialize) { 596 if (e instanceof InitializationException) { 597 throw (InitializationException) e; 598 } else { 599 LocalizableMessage message = ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get( 600 configuration.dn(), e.getCause() != null ? 601 e.getCause().getMessage() : stackTraceToSingleLineString(e)); 602 throw new InitializationException(message); 603 } 604 } 605 LocalizableMessage message = ERR_CONFIG_ENTRYCACHE_CANNOT_INITIALIZE_CACHE.get( 606 className, e.getCause() != null ? e.getCause().getMessage() : 607 stackTraceToSingleLineString(e)); 608 throw new InitializationException(message, e); 609 } 610 } 611 612}