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