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