/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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-2015 ForgeRock AS.
*/
package org.opends.server.core;
import java.util.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.util.Utils;
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.meta.EntryCacheCfgDefn;
import org.opends.server.admin.std.server.EntryCacheCfg;
import org.opends.server.admin.std.server.EntryCacheMonitorProviderCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.EntryCache;
import org.opends.server.config.ConfigConstants;
import org.opends.server.config.ConfigEntry;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.extensions.DefaultEntryCache;
import org.opends.server.monitors.EntryCacheMonitorProvider;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
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>
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The default entry cache. */
private DefaultEntryCache _defaultEntryCache;
/** The entry cache order map sorted by the cache level. */
@SuppressWarnings("rawtypes")
private SortedMap<Integer, EntryCache> cacheOrderMap = new TreeMap<>();
/** The entry cache to level map. */
private Map<DN,Integer> cacheNameToLevelMap = new HashMap<>();
/** Global entry cache monitor provider name. */
private static final String
DEFAULT_ENTRY_CACHE_MONITOR_PROVIDER = "Entry Caches";
private final ServerContext serverContext;
/**
* Creates a new instance of this entry cache config manager.
*
* @param serverContext
* The server context.
*/
public EntryCacheConfigManager(ServerContext serverContext)
{
this.serverContext = serverContext;
}
/**
* 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)
{
logger.traceException(e);
LocalizableMessage 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.valueOf(ConfigConstants.DN_ENTRY_CACHE_BASE);
entryCacheBase = DirectoryServer.getConfigEntry(configEntryDN);
} catch (Exception e) {
logger.traceException(e);
logger.warn(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY);
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)
{
logger.error(WARN_CONFIG_ENTRYCACHE_NO_CONFIG_ENTRY);
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()
&& cacheOrderMap.containsKey(configuration.getCacheLevel()))
{
// Log error and skip this cache.
logger.error(ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE,
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) {
logger.error(ie.getMessageObject());
}
}
}
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationChangeAcceptable(
EntryCacheCfg configuration,
List<LocalizableMessage> 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())
{
final ByteString normDN = configuration.dn().toNormalizedByteString();
if (cacheNameToLevelMap.containsKey(normDN)) {
int currentCacheLevel = cacheNameToLevelMap.get(normDN);
// 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(
configuration.dn(), configuration.getCacheLevel()));
status = false;
}
}
}
return status;
}
/** {@inheritDoc} */
@Override
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()) {
final DN dn = configuration.dn();
if (cacheNameToLevelMap.containsKey(dn))
{
int currentCacheLevel = cacheNameToLevelMap.get(dn);
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(dn, configuration.getCacheLevel());
}
}
}
final ConfigChangeResult changeResult = new ConfigChangeResult();
// 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.
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 initialize it.
try
{
loadAndInstallEntryCache (newClassName, configuration);
}
catch (InitializationException ie)
{
changeResult.addMessage (ie.getMessageObject());
changeResult.setResultCode (DirectoryServer.getServerErrorResultCode());
return changeResult;
}
return changeResult;
}
/** {@inheritDoc} */
@Override
public boolean isConfigurationAddAcceptable(
EntryCacheCfg configuration,
List<LocalizableMessage> unacceptableReasons
)
{
// returned status -- all is fine by default
// Check if there is another entry cache installed at the same level.
if (!cacheOrderMap.isEmpty()
&& cacheOrderMap.containsKey(configuration.getCacheLevel()))
{
unacceptableReasons.add(ERR_CONFIG_ENTRYCACHE_CONFIG_LEVEL_NOT_ACCEPTABLE.get(
configuration.dn(), configuration.getCacheLevel()));
return false;
}
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());
return false;
}
}
return true;
}
/** {@inheritDoc} */
@Override
public ConfigChangeResult applyConfigurationAdd(EntryCacheCfg configuration)
{
final ConfigChangeResult changeResult = new ConfigChangeResult();
// 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} */
@Override
public boolean isConfigurationDeleteAcceptable(
EntryCacheCfg configuration,
List<LocalizableMessage> 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} */
@Override
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());
}
final ConfigChangeResult changeResult = new ConfigChangeResult();
// 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().toNormalizedByteString());
// Push any changes made to the cache order map.
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(), configuration.getCacheLevel());
// Push any changes made to the cache order map.
setCacheOrder(cacheOrderMap);
// Install and register the monitor for this cache.
EntryCacheMonitorProvider monitor =
new EntryCacheMonitorProvider(configuration.dn().
rdn().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);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void setCacheOrder(SortedMap<Integer, EntryCache> cacheOrderMap)
{
_defaultEntryCache.setCacheOrder((SortedMap) cacheOrderMap);
}
/**
* 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 <T extends EntryCacheCfg> EntryCache<T> loadEntryCache(
String className,
T configuration,
boolean initialize
)
throws InitializationException
{
// If we this entry cache is already installed and active it
// should be present in the current cache order map, use it.
EntryCache<T> entryCache = null;
if (!cacheOrderMap.isEmpty()) {
entryCache = cacheOrderMap.get(configuration.getCacheLevel());
}
try
{
EntryCacheCfgDefn definition = EntryCacheCfgDefn.getInstance();
ClassPropertyDefinition propertyDefinition = definition
.getJavaClassPropertyDefinition();
@SuppressWarnings("unchecked")
Class<? extends EntryCache<T>> cacheClass =
(Class<? extends EntryCache<T>>) 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<T> cache;
if (initialize || entryCache == null) {
cache = cacheClass.newInstance();
} else {
cache = entryCache;
}
if (initialize)
{
cache.initializeEntryCache(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())
{
List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
if (!cache.isConfigurationAcceptable(configuration, unacceptableReasons))
{
String buffer = Utils.joinAsString(". ", unacceptableReasons);
throw new InitializationException(
ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get(configuration.dn(), buffer));
}
}
return cache;
}
catch (Exception e)
{
logger.traceException(e);
if (!initialize) {
if (e instanceof InitializationException) {
throw (InitializationException) e;
} else {
LocalizableMessage message = ERR_CONFIG_ENTRYCACHE_CONFIG_NOT_ACCEPTABLE.get(
configuration.dn(), e.getCause() != null ?
e.getCause().getMessage() : stackTraceToSingleLineString(e));
throw new InitializationException(message);
}
}
LocalizableMessage message = ERR_CONFIG_ENTRYCACHE_CANNOT_INITIALIZE_CACHE.get(
className, e.getCause() != null ? e.getCause().getMessage() :
stackTraceToSingleLineString(e));
throw new InitializationException(message, e);
}
}
}