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 }