/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions copyright 2013 ForgeRock AS. */ package org.opends.server.core; import org.opends.messages.Message; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.opends.server.admin.ClassPropertyDefinition; import org.opends.server.admin.server.ConfigurationAddListener; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.server.ConfigurationDeleteListener; import org.opends.server.admin.server.ServerManagementContext; import org.opends.server.admin.std.server.EntryCacheCfg; import org.opends.server.admin.std.server.RootCfg; import org.opends.server.admin.std.meta.EntryCacheCfgDefn; import org.opends.server.api.EntryCache; import org.opends.server.config.ConfigException; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.InitializationException; import org.opends.server.types.ResultCode; import org.opends.messages.MessageBuilder; import org.opends.server.admin.std.server.EntryCacheMonitorProviderCfg; import org.opends.server.api.Backend; import org.opends.server.config.ConfigConstants; import org.opends.server.config.ConfigEntry; import org.opends.server.extensions.DefaultEntryCache; import org.opends.server.monitors.EntryCacheMonitorProvider; import org.opends.server.types.DN; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.messages.ExtensionMessages.*; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines a utility that will be used to manage the configuration * for the Directory Server entry cache. The default entry cache is always * enabled. */ public class EntryCacheConfigManager implements ConfigurationChangeListener <EntryCacheCfg>, ConfigurationAddListener <EntryCacheCfg>, ConfigurationDeleteListener <EntryCacheCfg> { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); // The default entry cache. private DefaultEntryCache _defaultEntryCache = null; // The entry cache order map sorted by the cache level. private SortedMap<Integer, EntryCache<? extends EntryCacheCfg>> cacheOrderMap = new TreeMap<Integer, EntryCache<? extends EntryCacheCfg>>(); // The entry cache name to level map. private HashMap<String, Integer> cacheNameToLevelMap = new HashMap<String, Integer>(); // Global entry cache monitor provider name. private static final String DEFAULT_ENTRY_CACHE_MONITOR_PROVIDER = "Entry Caches"; /** * Creates a new instance of this entry cache config manager. */ public EntryCacheConfigManager() { // No implementation is required. } /** * Initializes the default entry cache. * This should only be called at Directory Server startup. * * @throws InitializationException If a problem occurs while trying to * install the default entry cache. */ public void initializeDefaultEntryCache() throws InitializationException { try { DefaultEntryCache defaultCache = new DefaultEntryCache(); defaultCache.initializeEntryCache(null); DirectoryServer.setEntryCache(defaultCache); _defaultEntryCache = defaultCache; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } Message message = ERR_CONFIG_ENTRYCACHE_CANNOT_INSTALL_DEFAULT_CACHE.get( stackTraceToSingleLineString(e)); throw new InitializationException(message, e); } } /** * Initializes the configuration associated with the Directory Server entry * cache. This should only be called at Directory Server startup. If an * error occurs, then a message will be logged for each entry cache that is * failed to initialize. * * @throws ConfigException If a configuration problem causes the entry * cache initialization process to fail. */ public void initializeEntryCache() throws ConfigException { // Get the root configuration object. ServerManagementContext managementContext = ServerManagementContext.getInstance(); RootCfg rootConfiguration = managementContext.getRootConfiguration(); // Default entry cache should be already installed with // <CODE>initializeDefaultEntryCache()</CODE> method so // that there will be one even if we encounter a problem // later. // Register as an add and delete listener with the root configuration so we // can be notified if any entry cache entry is added or removed. rootConfiguration.addEntryCacheAddListener(this); rootConfiguration.addEntryCacheDeleteListener(this); // Get the base entry cache configuration entry. ConfigEntry entryCacheBase; try { DN configEntryDN = DN.decode(ConfigConstants.DN_ENTRY_CACHE_BASE); entryCacheBase = DirectoryServer.getConfigEntry(configEntryDN); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } logError(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY.get()); return; } // If the configuration base entry is null, then assume it doesn't exist. // At least that entry must exist in the configuration, even if there are // no entry cache defined below it. if (entryCacheBase == null) { logError(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY.get()); return; } // Initialize every entry cache configured. for (String cacheName : rootConfiguration.listEntryCaches()) { // Get the entry cache configuration. EntryCacheCfg configuration = rootConfiguration.getEntryCache(cacheName); // At this point, we have a configuration entry. Register a change // listener with it so we can be notified of changes to it over time. configuration.addChangeListener(this); // Check if there is another entry cache installed at the same level. if (!cacheOrderMap.isEmpty()) { if (cacheOrderMap.containsKey(configuration.getCacheLevel())) { // Log error and skip this cache. logError(ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get( String.valueOf(configuration.dn()), configuration.getCacheLevel())); continue; } } // Initialize the entry cache. if (configuration.isEnabled()) { // Load the entry cache implementation class and install the entry // cache with the server. String className = configuration.getJavaClass(); try { loadAndInstallEntryCache(className, configuration); } catch (InitializationException ie) { logError(ie.getMessageObject()); } } } // If requested preload the entry cache. if (rootConfiguration.getGlobalConfiguration().isEntryCachePreload() && !cacheOrderMap.isEmpty()) { // Preload from every active public backend. Map<DN, Backend> baseDNMap = DirectoryServer.getPublicNamingContexts(); Set<Backend> proccessedBackends = new HashSet<Backend>(); for (Backend backend : baseDNMap.values()) { if (!proccessedBackends.contains(backend)) { proccessedBackends.add(backend); try { backend.preloadEntryCache(); } catch (UnsupportedOperationException ex) { // Some backend implementations might not support entry // cache preload. Log a warning and continue. Message message = WARN_CACHE_PRELOAD_BACKEND_FAILED.get( backend.getBackendID()); logError(message); continue; } } } } } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( EntryCacheCfg configuration, List<Message> unacceptableReasons ) { // returned status -- all is fine by default boolean status = true; // Get the name of the class and make sure we can instantiate it as an // entry cache. String className = configuration.getJavaClass(); try { // Load the class but don't initialize it. loadEntryCache(className, configuration, false); } catch (InitializationException ie) { unacceptableReasons.add(ie.getMessageObject()); status = false; } if (!cacheOrderMap.isEmpty() && !cacheNameToLevelMap.isEmpty() && (cacheNameToLevelMap.get( configuration.dn().toNormalizedString()) != null)) { int currentCacheLevel = cacheNameToLevelMap.get( configuration.dn().toNormalizedString()); // Check if there any existing cache at the same level. if ((currentCacheLevel != configuration.getCacheLevel()) && (cacheOrderMap.containsKey(configuration.getCacheLevel()))) { unacceptableReasons.add( ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get( String.valueOf(configuration.dn()), configuration.getCacheLevel())); status = false; } } return status; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( EntryCacheCfg configuration ) { EntryCache<? extends EntryCacheCfg> entryCache = null; // If we this entry cache is already installed and active it // should be present in the cache maps, if so use it. if (!cacheOrderMap.isEmpty() && !cacheNameToLevelMap.isEmpty() && (cacheNameToLevelMap.get( configuration.dn().toNormalizedString()) != null)) { int currentCacheLevel = cacheNameToLevelMap.get( configuration.dn().toNormalizedString()); entryCache = cacheOrderMap.get(currentCacheLevel); // Check if the existing cache just shifted its level. if (currentCacheLevel != configuration.getCacheLevel()) { // Update the maps then. cacheOrderMap.remove(currentCacheLevel); cacheOrderMap.put(configuration.getCacheLevel(), entryCache); cacheNameToLevelMap.put(configuration.dn().toNormalizedString(), configuration.getCacheLevel()); } } // Returned result. ConfigChangeResult changeResult = new ConfigChangeResult( ResultCode.SUCCESS, false, new ArrayList<Message>() ); // If an entry cache was installed then remove it. if (!configuration.isEnabled()) { configuration.getCacheLevel(); if (entryCache != null) { EntryCacheMonitorProvider monitor = entryCache.getEntryCacheMonitor(); if (monitor != null) { DirectoryServer.deregisterMonitorProvider(monitor); monitor.finalizeMonitorProvider(); entryCache.setEntryCacheMonitor(null); } entryCache.finalizeEntryCache(); cacheOrderMap.remove(configuration.getCacheLevel()); entryCache = null; } return changeResult; } // Push any changes made to the cache order map. _defaultEntryCache.setCacheOrder(cacheOrderMap); // At this point, new configuration is enabled... // If the current entry cache is already enabled then we don't do // anything unless the class has changed in which case we should // indicate that administrative action is required. String newClassName = configuration.getJavaClass(); if ( entryCache != null) { String curClassName = entryCache.getClass().getName(); boolean classIsNew = (! newClassName.equals (curClassName)); if (classIsNew) { changeResult.setAdminActionRequired (true); } return changeResult; } // New entry cache is enabled and there were no previous one. // Instantiate the new class and initalize it. try { loadAndInstallEntryCache (newClassName, configuration); } catch (InitializationException ie) { changeResult.addMessage (ie.getMessageObject()); changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); return changeResult; } return changeResult; } /** * {@inheritDoc} */ public boolean isConfigurationAddAcceptable( EntryCacheCfg configuration, List<Message> unacceptableReasons ) { // returned status -- all is fine by default boolean status = true; // Check if there is another entry cache installed at the same level. if (!cacheOrderMap.isEmpty()) { if (cacheOrderMap.containsKey(configuration.getCacheLevel())) { unacceptableReasons.add( ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get( String.valueOf(configuration.dn()), configuration.getCacheLevel())); status = false; return status; } } if (configuration.isEnabled()) { // Get the name of the class and make sure we can instantiate it as // an entry cache. String className = configuration.getJavaClass(); try { // Load the class but don't initialize it. loadEntryCache(className, configuration, false); } catch (InitializationException ie) { unacceptableReasons.add (ie.getMessageObject()); status = false; } } return status; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationAdd( EntryCacheCfg configuration ) { // Returned result. ConfigChangeResult changeResult = new ConfigChangeResult( ResultCode.SUCCESS, false, new ArrayList<Message>() ); // Register a change listener with it so we can be notified of changes // to it over time. configuration.addChangeListener(this); if (configuration.isEnabled()) { // Instantiate the class as an entry cache and initialize it. String className = configuration.getJavaClass(); try { loadAndInstallEntryCache (className, configuration); } catch (InitializationException ie) { changeResult.addMessage (ie.getMessageObject()); changeResult.setResultCode (DirectoryServer.getServerErrorResultCode()); return changeResult; } } return changeResult; } /** * {@inheritDoc} */ public boolean isConfigurationDeleteAcceptable( EntryCacheCfg configuration, List<Message> unacceptableReasons ) { // If we've gotten to this point, then it is acceptable as far as we are // concerned. If it is unacceptable according to the configuration, then // the entry cache itself will make that determination. return true; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationDelete( EntryCacheCfg configuration ) { EntryCache<? extends EntryCacheCfg> entryCache = null; // If we this entry cache is already installed and active it // should be present in the current cache order map, use it. if (!cacheOrderMap.isEmpty()) { entryCache = cacheOrderMap.get(configuration.getCacheLevel()); } // Returned result. ConfigChangeResult changeResult = new ConfigChangeResult( ResultCode.SUCCESS, false, new ArrayList<Message>() ); // If the entry cache was installed then remove it. if (entryCache != null) { EntryCacheMonitorProvider monitor = entryCache.getEntryCacheMonitor(); if (monitor != null) { DirectoryServer.deregisterMonitorProvider(monitor); monitor.finalizeMonitorProvider(); entryCache.setEntryCacheMonitor(null); } entryCache.finalizeEntryCache(); cacheOrderMap.remove(configuration.getCacheLevel()); cacheNameToLevelMap.remove(configuration.dn().toNormalizedString()); // Push any changes made to the cache order map. _defaultEntryCache.setCacheOrder(cacheOrderMap); entryCache = null; } return changeResult; } /** * Loads the specified class, instantiates it as an entry cache, * and optionally initializes that instance. Any initialize entry * cache is registered in the server. * * @param className The fully-qualified name of the entry cache * class to load, instantiate, and initialize. * @param configuration The configuration to use to initialize the * entry cache, or {@code null} if the * entry cache should not be initialized. * * @throws InitializationException If a problem occurred while attempting * to initialize the entry cache. */ private void loadAndInstallEntryCache( String className, EntryCacheCfg configuration ) throws InitializationException { // Get the root configuration object. ServerManagementContext managementContext = ServerManagementContext.getInstance(); RootCfg rootConfiguration = managementContext.getRootConfiguration(); // Load the entry cache class... EntryCache<? extends EntryCacheCfg> entryCache = loadEntryCache (className, configuration, true); // ... and install the entry cache in the server. // Add this entry cache to the current cache config maps. cacheOrderMap.put(configuration.getCacheLevel(), entryCache); cacheNameToLevelMap.put(configuration.dn().toNormalizedString(), configuration.getCacheLevel()); // Push any changes made to the cache order map. _defaultEntryCache.setCacheOrder(cacheOrderMap); // Install and register the monitor for this cache. EntryCacheMonitorProvider monitor = new EntryCacheMonitorProvider(configuration.dn(). getRDN().getAttributeValue(0).toString(), entryCache); try { monitor.initializeMonitorProvider((EntryCacheMonitorProviderCfg) rootConfiguration.getMonitorProvider( DEFAULT_ENTRY_CACHE_MONITOR_PROVIDER)); } catch (ConfigException ce) { // ConfigException here means that either the entry cache monitor // config entry is not present or the monitor is not enabled. In // either case that means no monitor provider for this cache. return; } entryCache.setEntryCacheMonitor(monitor); DirectoryServer.registerMonitorProvider(monitor); } /** * Loads the specified class, instantiates it as an entry cache, and * optionally initializes that instance. * * @param className The fully-qualified name of the entry cache class * to load, instantiate, and initialize. * @param configuration The configuration to use to initialize the entry * cache. It must not be {@code null}. * @param initialize Indicates whether the entry cache instance should be * initialized. * * @return The possibly initialized entry cache. * * @throws InitializationException If a problem occurred while attempting * to initialize the entry cache. */ private EntryCache<? extends EntryCacheCfg> loadEntryCache( String className, EntryCacheCfg configuration, boolean initialize ) throws InitializationException { EntryCache<?> entryCache = null; // If we this entry cache is already installed and active it // should be present in the current cache order map, use it. if (!cacheOrderMap.isEmpty()) { entryCache = cacheOrderMap.get(configuration.getCacheLevel()); } try { EntryCacheCfgDefn definition = EntryCacheCfgDefn.getInstance(); ClassPropertyDefinition propertyDefinition = definition .getJavaClassPropertyDefinition(); @SuppressWarnings("unchecked") Class<? extends EntryCache<?>> cacheClass = (Class<? extends EntryCache<?>>) propertyDefinition .loadClass(className, EntryCache.class); // If there is some entry cache instance already initialized work with // it instead of creating a new one unless explicit init is requested. EntryCache<? extends EntryCacheCfg> cache; if (initialize || (entryCache == null)) { cache = (EntryCache<? extends EntryCacheCfg>) cacheClass.newInstance(); } else { cache = (EntryCache<? extends EntryCacheCfg>) entryCache; } if (initialize) { Method method = cache.getClass().getMethod("initializeEntryCache", configuration.configurationClass()); method.invoke(cache, configuration); } // This will check if configuration is acceptable on disabled // and uninitialized cache instance that has no "acceptable" // change listener registered to invoke and verify on its own. else if (!configuration.isEnabled()) { Method method = cache.getClass().getMethod("isConfigurationAcceptable", EntryCacheCfg.class, List.class); List<Message> unacceptableReasons = new ArrayList<Message>(); Boolean acceptable = (Boolean) method.invoke(cache, configuration, unacceptableReasons); if (! acceptable) { MessageBuilder buffer = new MessageBuilder(); if (! unacceptableReasons.isEmpty()) { Iterator<Message> iterator = unacceptableReasons.iterator(); buffer.append(iterator.next()); while (iterator.hasNext()) { buffer.append(". "); buffer.append(iterator.next()); } } Message message = ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get( String.valueOf(configuration.dn()), buffer.toString()); throw new InitializationException(message); } } return cache; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } if (!initialize) { if (e instanceof InitializationException) { throw (InitializationException) e; } else { Message message = ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get( String.valueOf(configuration.dn()), e.getCause() != null ? e.getCause().getMessage() : stackTraceToSingleLineString(e)); throw new InitializationException(message); } } Message message = ERR_CONFIG_ENTRYCACHE_CANNOT_INITIALIZE_CACHE.get( className, (e.getCause() != null ? e.getCause().getMessage() : stackTraceToSingleLineString(e))); throw new InitializationException(message, e); } } }