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    }