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