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