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}