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.extensions;
028 import org.opends.messages.Message;
029
030
031
032 import java.util.ArrayList;
033 import java.util.HashMap;
034 import java.util.HashSet;
035 import java.util.Iterator;
036 import java.util.LinkedHashMap;
037 import java.util.List;
038 import java.util.Map;
039 import java.util.concurrent.TimeUnit;
040 import java.util.concurrent.locks.Lock;
041 import java.util.concurrent.locks.ReentrantReadWriteLock;
042
043 import org.opends.server.admin.server.ConfigurationChangeListener;
044 import org.opends.server.admin.std.server.EntryCacheCfg;
045 import org.opends.server.admin.std.server.FIFOEntryCacheCfg;
046 import org.opends.server.api.Backend;
047 import org.opends.server.api.EntryCache;
048 import org.opends.server.config.ConfigException;
049 import org.opends.server.core.DirectoryServer;
050 import org.opends.server.loggers.debug.DebugTracer;
051 import org.opends.server.types.CacheEntry;
052 import org.opends.server.types.ConfigChangeResult;
053 import org.opends.server.types.DebugLogLevel;
054 import org.opends.server.types.DN;
055 import org.opends.server.types.Entry;
056 import org.opends.server.types.InitializationException;
057 import org.opends.server.types.SearchFilter;
058 import org.opends.server.types.Attribute;
059 import org.opends.server.util.ServerConstants;
060 import org.opends.messages.MessageBuilder;
061
062 import static org.opends.server.loggers.debug.DebugLogger.*;
063 import static org.opends.messages.ExtensionMessages.*;
064
065
066
067 /**
068 * This class defines a Directory Server entry cache that uses a FIFO to keep
069 * track of the entries. Entries that have been in the cache the longest are
070 * the most likely candidates for purging if space is needed. In contrast to
071 * other cache structures, the selection of entries to purge is not based on
072 * how frequently or recently the entries have been accessed. This requires
073 * significantly less locking (it will only be required when an entry is added
074 * or removed from the cache, rather than each time an entry is accessed).
075 * <BR><BR>
076 * Cache sizing is based on the percentage of free memory within the JVM, such
077 * that if enough memory is free, then adding an entry to the cache will not
078 * require purging, but if more than a specified percentage of the available
079 * memory within the JVM is already consumed, then one or more entries will need
080 * to be removed in order to make room for a new entry. It is also possible to
081 * configure a maximum number of entries for the cache. If this is specified,
082 * then the number of entries will not be allowed to exceed this value, but it
083 * may not be possible to hold this many entries if the available memory fills
084 * up first.
085 * <BR><BR>
086 * Other configurable parameters for this cache include the maximum length of
087 * time to block while waiting to acquire a lock, and a set of filters that may
088 * be used to define criteria for determining which entries are stored in the
089 * cache. If a filter list is provided, then only entries matching at least one
090 * of the given filters will be stored in the cache.
091 */
092 public class FIFOEntryCache
093 extends EntryCache <FIFOEntryCacheCfg>
094 implements ConfigurationChangeListener<FIFOEntryCacheCfg>
095 {
096 /**
097 * The tracer object for the debug logger.
098 */
099 private static final DebugTracer TRACER = getTracer();
100
101 /**
102 * The reference to the Java runtime used to determine the amount of memory
103 * currently in use.
104 */
105 private static final Runtime runtime = Runtime.getRuntime();
106
107 // The mapping between entry backends/IDs and entries.
108 private HashMap<Backend,HashMap<Long,CacheEntry>> idMap;
109
110 // The mapping between DNs and entries.
111 private LinkedHashMap<DN,CacheEntry> dnMap;
112
113 // The lock used to provide threadsafe access when changing the contents of
114 // the cache.
115 private ReentrantReadWriteLock cacheLock;
116 private Lock cacheWriteLock;
117 private Lock cacheReadLock;
118
119 // The maximum amount of memory in bytes that the JVM will be allowed to use
120 // before we need to start purging entries.
121 private long maxAllowedMemory;
122
123 // The maximum number of entries that may be held in the cache.
124 private long maxEntries;
125
126 // Currently registered configuration object.
127 private FIFOEntryCacheCfg registeredConfiguration;
128
129
130
131 /**
132 * Creates a new instance of this FIFO entry cache.
133 */
134 public FIFOEntryCache()
135 {
136 super();
137
138
139 // All initialization should be performed in the initializeEntryCache.
140 }
141
142
143
144 /**
145 * {@inheritDoc}
146 */
147 public void initializeEntryCache(
148 FIFOEntryCacheCfg configuration
149 )
150 throws ConfigException, InitializationException
151 {
152 registeredConfiguration = configuration;
153 configuration.addFIFOChangeListener (this);
154
155 // Initialize the cache structures.
156 idMap = new HashMap<Backend,HashMap<Long,CacheEntry>>();
157 dnMap = new LinkedHashMap<DN,CacheEntry>();
158
159 // Initialize locks.
160 cacheLock = new ReentrantReadWriteLock(true);
161 cacheWriteLock = cacheLock.writeLock();
162 cacheReadLock = cacheLock.readLock();
163
164 // Read configuration and apply changes.
165 boolean applyChanges = true;
166 ArrayList<Message> errorMessages = new ArrayList<Message>();
167 EntryCacheCommon.ConfigErrorHandler errorHandler =
168 EntryCacheCommon.getConfigErrorHandler (
169 EntryCacheCommon.ConfigPhase.PHASE_INIT, null, errorMessages
170 );
171 if (!processEntryCacheConfig(configuration, applyChanges, errorHandler)) {
172 MessageBuilder buffer = new MessageBuilder();
173 if (!errorMessages.isEmpty()) {
174 Iterator<Message> iterator = errorMessages.iterator();
175 buffer.append(iterator.next());
176 while (iterator.hasNext()) {
177 buffer.append(". ");
178 buffer.append(iterator.next());
179 }
180 }
181 Message message = ERR_FIFOCACHE_CANNOT_INITIALIZE.get(buffer.toString());
182 throw new ConfigException(message);
183 }
184 }
185
186
187
188 /**
189 * {@inheritDoc}
190 */
191 public void finalizeEntryCache()
192 {
193 cacheWriteLock.lock();
194
195 try {
196 registeredConfiguration.removeFIFOChangeListener(this);
197
198 // Release all memory currently in use by this cache.
199 try {
200 idMap.clear();
201 dnMap.clear();
202 } catch (Exception e) {
203 // This should never happen.
204 if (debugEnabled()) {
205 TRACER.debugCaught(DebugLogLevel.ERROR, e);
206 }
207 }
208 } finally {
209 cacheWriteLock.unlock();
210 }
211 }
212
213
214
215 /**
216 * {@inheritDoc}
217 */
218 public boolean containsEntry(DN entryDN)
219 {
220 if (entryDN == null) {
221 return false;
222 }
223
224 // Indicate whether the DN map contains the specified DN.
225 cacheReadLock.lock();
226 try {
227 return dnMap.containsKey(entryDN);
228 } finally {
229 cacheReadLock.unlock();
230 }
231 }
232
233
234
235 /**
236 * {@inheritDoc}
237 */
238 public Entry getEntry(DN entryDN)
239 {
240 // Simply return the entry from the DN map.
241 cacheReadLock.lock();
242 try {
243 CacheEntry e = dnMap.get(entryDN);
244 if (e == null) {
245 // Indicate cache miss.
246 cacheMisses.getAndIncrement();
247 return null;
248 } else {
249 // Indicate cache hit.
250 cacheHits.getAndIncrement();
251 return e.getEntry();
252 }
253 } finally {
254 cacheReadLock.unlock();
255 }
256 }
257
258
259
260 /**
261 * {@inheritDoc}
262 */
263 public long getEntryID(DN entryDN)
264 {
265 // Simply return the ID from the DN map.
266 cacheReadLock.lock();
267 try {
268 CacheEntry e = dnMap.get(entryDN);
269 if (e == null) {
270 return -1;
271 } else {
272 return e.getEntryID();
273 }
274 } finally {
275 cacheReadLock.unlock();
276 }
277 }
278
279
280
281 /**
282 * {@inheritDoc}
283 */
284 public DN getEntryDN(Backend backend, long entryID)
285 {
286 // Locate specific backend map and return the entry DN by ID.
287 cacheReadLock.lock();
288 try {
289 HashMap<Long, CacheEntry> backendMap = idMap.get(backend);
290 if (backendMap != null) {
291 CacheEntry e = backendMap.get(entryID);
292 if (e != null) {
293 return e.getDN();
294 }
295 }
296 return null;
297 } finally {
298 cacheReadLock.unlock();
299 }
300 }
301
302
303
304 /**
305 * {@inheritDoc}
306 */
307 public void putEntry(Entry entry, Backend backend, long entryID)
308 {
309 // Create the cache entry based on the provided information.
310 CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID);
311
312
313 // Obtain a lock on the cache. If this fails, then don't do anything.
314 try
315 {
316 if (!cacheWriteLock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
317 {
318 return;
319 }
320 }
321 catch (Exception e)
322 {
323 if (debugEnabled())
324 {
325 TRACER.debugCaught(DebugLogLevel.ERROR, e);
326 }
327
328 return;
329 }
330
331
332 // At this point, we hold the lock. No matter what, we must release the
333 // lock before leaving this method, so do that in a finally block.
334 try
335 {
336 // See if the current memory usage is within acceptable constraints. If
337 // so, then add the entry to the cache (or replace it if it is already
338 // present). If not, then remove an existing entry and don't add the new
339 // entry.
340 long usedMemory = runtime.totalMemory() - runtime.freeMemory();
341 if (usedMemory > maxAllowedMemory)
342 {
343 Iterator<CacheEntry> iterator = dnMap.values().iterator();
344 if (iterator.hasNext())
345 {
346 CacheEntry ce = iterator.next();
347 iterator.remove();
348
349 HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
350 if (m != null)
351 {
352 m.remove(ce.getEntryID());
353 }
354 }
355 }
356 else
357 {
358 // Add the entry to the cache. This will replace it if it is already
359 // present and add it if it isn't.
360 dnMap.put(entry.getDN(), cacheEntry);
361
362 HashMap<Long,CacheEntry> map = idMap.get(backend);
363 if (map == null)
364 {
365 map = new HashMap<Long,CacheEntry>();
366 map.put(entryID, cacheEntry);
367 idMap.put(backend, map);
368 }
369 else
370 {
371 map.put(entryID, cacheEntry);
372 }
373
374
375 // See if a cap has been placed on the maximum number of entries in the
376 // cache. If so, then see if we have exceeded it and we need to purge
377 // entries until we're within the limit.
378 int entryCount = dnMap.size();
379 if ((maxEntries > 0) && (entryCount > maxEntries))
380 {
381 Iterator<CacheEntry> iterator = dnMap.values().iterator();
382 while (iterator.hasNext() && (entryCount > maxEntries))
383 {
384 CacheEntry ce = iterator.next();
385 iterator.remove();
386
387 HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
388 if (m != null)
389 {
390 m.remove(ce.getEntryID());
391 }
392
393 entryCount--;
394 }
395 }
396 }
397 }
398 catch (Exception e)
399 {
400 if (debugEnabled())
401 {
402 TRACER.debugCaught(DebugLogLevel.ERROR, e);
403 }
404
405 return;
406 }
407 finally
408 {
409 cacheWriteLock.unlock();
410 }
411 }
412
413
414
415 /**
416 * {@inheritDoc}
417 */
418 public boolean putEntryIfAbsent(Entry entry, Backend backend, long entryID)
419 {
420 // Create the cache entry based on the provided information.
421 CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID);
422
423
424 // Obtain a lock on the cache. If this fails, then don't do anything.
425 try
426 {
427 if (!cacheWriteLock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
428 {
429 // We can't rule out the possibility of a conflict, so return false.
430 return false;
431 }
432 }
433 catch (Exception e)
434 {
435 if (debugEnabled())
436 {
437 TRACER.debugCaught(DebugLogLevel.ERROR, e);
438 }
439
440 // We can't rule out the possibility of a conflict, so return false.
441 return false;
442 }
443
444
445 // At this point, we hold the lock. No matter what, we must release the
446 // lock before leaving this method, so do that in a finally block.
447 try
448 {
449 // See if the entry already exists in the cache. If it does, then we will
450 // fail and not actually store the entry.
451 if (dnMap.containsKey(entry.getDN()))
452 {
453 return false;
454 }
455
456 // See if the current memory usage is within acceptable constraints. If
457 // so, then add the entry to the cache (or replace it if it is already
458 // present). If not, then remove an existing entry and don't add the new
459 // entry.
460 long usedMemory = runtime.totalMemory() - runtime.freeMemory();
461 if (usedMemory > maxAllowedMemory)
462 {
463 Iterator<CacheEntry> iterator = dnMap.values().iterator();
464 if (iterator.hasNext())
465 {
466 CacheEntry ce = iterator.next();
467 iterator.remove();
468
469 HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
470 if (m != null)
471 {
472 m.remove(ce.getEntryID());
473 }
474 }
475 }
476 else
477 {
478 // Add the entry to the cache. This will replace it if it is already
479 // present and add it if it isn't.
480 dnMap.put(entry.getDN(), cacheEntry);
481
482 HashMap<Long,CacheEntry> map = idMap.get(backend);
483 if (map == null)
484 {
485 map = new HashMap<Long,CacheEntry>();
486 map.put(entryID, cacheEntry);
487 idMap.put(backend, map);
488 }
489 else
490 {
491 map.put(entryID, cacheEntry);
492 }
493
494
495 // See if a cap has been placed on the maximum number of entries in the
496 // cache. If so, then see if we have exceeded it and we need to purge
497 // entries until we're within the limit.
498 int entryCount = dnMap.size();
499 if ((maxEntries > 0) && (entryCount > maxEntries))
500 {
501 Iterator<CacheEntry> iterator = dnMap.values().iterator();
502 while (iterator.hasNext() && (entryCount > maxEntries))
503 {
504 CacheEntry ce = iterator.next();
505 iterator.remove();
506
507 HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
508 if (m != null)
509 {
510 m.remove(ce.getEntryID());
511 }
512
513 entryCount--;
514 }
515 }
516 }
517
518
519 // We'll always return true in this case, even if we didn't actually add
520 // the entry due to memory constraints.
521 return true;
522 }
523 catch (Exception e)
524 {
525 if (debugEnabled())
526 {
527 TRACER.debugCaught(DebugLogLevel.ERROR, e);
528 }
529
530 // We can't be sure there wasn't a conflict, so return false.
531 return false;
532 }
533 finally
534 {
535 cacheWriteLock.unlock();
536 }
537 }
538
539
540
541 /**
542 * {@inheritDoc}
543 */
544 public void removeEntry(DN entryDN)
545 {
546 // Acquire the lock on the cache. We should not return until the entry is
547 // removed, so we will block until we can obtain the lock.
548 // FIXME -- An alternate approach could be to block for a maximum length of
549 // time and then if it fails then put it in a queue for processing by some
550 // other thread before it releases the lock.
551 cacheWriteLock.lock();
552
553
554 // At this point, it is absolutely critical that we always release the lock
555 // before leaving this method, so do so in a finally block.
556 try
557 {
558 // Check the DN cache to see if the entry exists. If not, then don't do
559 // anything.
560 CacheEntry entry = dnMap.remove(entryDN);
561 if (entry == null)
562 {
563 return;
564 }
565
566 Backend backend = entry.getBackend();
567
568 // Try to remove the entry from the ID list as well.
569 Map<Long,CacheEntry> map = idMap.get(backend);
570 if (map == null)
571 {
572 // This should't happen, but the entry isn't cached in the ID map so
573 // we can return.
574 return;
575 }
576
577 map.remove(entry.getEntryID());
578
579 // If this backend becomes empty now remove it from the idMap map.
580 if (map.isEmpty())
581 {
582 idMap.remove(backend);
583 }
584 }
585 catch (Exception e)
586 {
587 if (debugEnabled())
588 {
589 TRACER.debugCaught(DebugLogLevel.ERROR, e);
590 }
591
592 // This shouldn't happen, but there's not much that we can do if it does.
593 }
594 finally
595 {
596 cacheWriteLock.unlock();
597 }
598 }
599
600
601
602 /**
603 * {@inheritDoc}
604 */
605 public void clear()
606 {
607 // Acquire a lock on the cache. We should not return until the cache has
608 // been cleared, so we will block until we can obtain the lock.
609 cacheWriteLock.lock();
610
611
612 // At this point, it is absolutely critical that we always release the lock
613 // before leaving this method, so do so in a finally block.
614 try
615 {
616 // Clear the DN cache.
617 dnMap.clear();
618
619 // Clear the ID cache.
620 idMap.clear();
621 }
622 catch (Exception e)
623 {
624 if (debugEnabled())
625 {
626 TRACER.debugCaught(DebugLogLevel.ERROR, e);
627 }
628
629 // This shouldn't happen, but there's not much that we can do if it does.
630 }
631 finally
632 {
633 cacheWriteLock.unlock();
634 }
635 }
636
637
638
639 /**
640 * {@inheritDoc}
641 */
642 public void clearBackend(Backend backend)
643 {
644 // Acquire a lock on the cache. We should not return until the cache has
645 // been cleared, so we will block until we can obtain the lock.
646 cacheWriteLock.lock();
647
648
649 // At this point, it is absolutely critical that we always release the lock
650 // before leaving this method, so do so in a finally block.
651 try
652 {
653 // Remove all references to entries for this backend from the ID cache.
654 HashMap<Long,CacheEntry> map = idMap.remove(backend);
655 if (map == null)
656 {
657 // No entries were in the cache for this backend, so we can return
658 // without doing anything.
659 return;
660 }
661
662
663 // Unfortunately, there is no good way to dump the entries from the DN
664 // cache based on their backend, so we will need to iterate through the
665 // entries in the ID map and do it manually. Since this could take a
666 // while, we'll periodically release and re-acquire the lock in case
667 // anyone else is waiting on it so this doesn't become a stop-the-world
668 // event as far as the cache is concerned.
669 int entriesDeleted = 0;
670 for (CacheEntry e : map.values())
671 {
672 dnMap.remove(e.getEntry().getDN());
673 entriesDeleted++;
674
675 if ((entriesDeleted % 1000) == 0)
676 {
677 cacheWriteLock.unlock();
678 Thread.currentThread().yield();
679 cacheWriteLock.lock();
680 }
681 }
682 }
683 catch (Exception e)
684 {
685 if (debugEnabled())
686 {
687 TRACER.debugCaught(DebugLogLevel.ERROR, e);
688 }
689
690 // This shouldn't happen, but there's not much that we can do if it does.
691 }
692 finally
693 {
694 cacheWriteLock.unlock();
695 }
696 }
697
698
699
700 /**
701 * {@inheritDoc}
702 */
703 public void clearSubtree(DN baseDN)
704 {
705 // Determine which backend should be used for the provided base DN. If
706 // there is none, then we don't need to do anything.
707 Backend backend = DirectoryServer.getBackend(baseDN);
708 if (backend == null)
709 {
710 return;
711 }
712
713
714 // Acquire a lock on the cache. We should not return until the cache has
715 // been cleared, so we will block until we can obtain the lock.
716 cacheWriteLock.lock();
717
718
719 // At this point, it is absolutely critical that we always release the lock
720 // before leaving this method, so do so in a finally block.
721 try
722 {
723 clearSubtree(baseDN, backend);
724 }
725 catch (Exception e)
726 {
727 if (debugEnabled())
728 {
729 TRACER.debugCaught(DebugLogLevel.ERROR, e);
730 }
731
732 // This shouldn't happen, but there's not much that we can do if it does.
733 }
734 finally
735 {
736 cacheWriteLock.unlock();
737 }
738 }
739
740
741
742 /**
743 * Clears all entries at or below the specified base DN that are associated
744 * with the given backend. The caller must already hold the cache lock.
745 *
746 * @param baseDN The base DN below which all entries should be flushed.
747 * @param backend The backend for which to remove the appropriate entries.
748 */
749 private void clearSubtree(DN baseDN, Backend backend)
750 {
751 // See if there are any entries for the provided backend in the cache. If
752 // not, then return.
753 HashMap<Long,CacheEntry> map = idMap.get(backend);
754 if (map == null)
755 {
756 // No entries were in the cache for this backend, so we can return without
757 // doing anything.
758 return;
759 }
760
761
762 // Since the provided base DN could hold a subset of the information in the
763 // specified backend, we will have to do this by iterating through all the
764 // entries for that backend. Since this could take a while, we'll
765 // periodically release and re-acquire the lock in case anyone else is
766 // waiting on it so this doesn't become a stop-the-world event as far as the
767 // cache is concerned.
768 int entriesExamined = 0;
769 Iterator<CacheEntry> iterator = map.values().iterator();
770 while (iterator.hasNext())
771 {
772 CacheEntry e = iterator.next();
773 DN entryDN = e.getEntry().getDN();
774 if (entryDN.isDescendantOf(baseDN))
775 {
776 iterator.remove();
777 dnMap.remove(entryDN);
778 }
779
780 entriesExamined++;
781 if ((entriesExamined % 1000) == 0)
782 {
783 cacheWriteLock.unlock();
784 Thread.currentThread().yield();
785 cacheWriteLock.lock();
786 }
787 }
788
789
790 // See if the backend has any subordinate backends. If so, then process
791 // them recursively.
792 for (Backend subBackend : backend.getSubordinateBackends())
793 {
794 boolean isAppropriate = false;
795 for (DN subBase : subBackend.getBaseDNs())
796 {
797 if (subBase.isDescendantOf(baseDN))
798 {
799 isAppropriate = true;
800 break;
801 }
802 }
803
804 if (isAppropriate)
805 {
806 clearSubtree(baseDN, subBackend);
807 }
808 }
809 }
810
811
812
813 /**
814 * {@inheritDoc}
815 */
816 public void handleLowMemory()
817 {
818 // Grab the lock on the cache and wait until we have it.
819 cacheWriteLock.lock();
820
821
822 // At this point, it is absolutely critical that we always release the lock
823 // before leaving this method, so do so in a finally block.
824 try
825 {
826 // See how many entries are in the cache. If there are less than 1000,
827 // then we'll dump all of them. Otherwise, we'll dump 10% of the entries.
828 int numEntries = dnMap.size();
829 if (numEntries < 1000)
830 {
831 dnMap.clear();
832 idMap.clear();
833 }
834 else
835 {
836 int numToDrop = numEntries / 10;
837 Iterator<CacheEntry> iterator = dnMap.values().iterator();
838 while (iterator.hasNext() && (numToDrop > 0))
839 {
840 CacheEntry entry = iterator.next();
841 iterator.remove();
842
843 HashMap<Long,CacheEntry> m = idMap.get(entry.getBackend());
844 if (m != null)
845 {
846 m.remove(entry.getEntryID());
847 }
848
849 numToDrop--;
850 }
851 }
852 }
853 catch (Exception e)
854 {
855 if (debugEnabled())
856 {
857 TRACER.debugCaught(DebugLogLevel.ERROR, e);
858 }
859
860 // This shouldn't happen, but there's not much that we can do if it does.
861 }
862 finally
863 {
864 cacheWriteLock.unlock();
865 }
866 }
867
868
869
870 /**
871 * {@inheritDoc}
872 */
873 @Override()
874 public boolean isConfigurationAcceptable(EntryCacheCfg configuration,
875 List<Message> unacceptableReasons)
876 {
877 FIFOEntryCacheCfg config = (FIFOEntryCacheCfg) configuration;
878 return isConfigurationChangeAcceptable(config, unacceptableReasons);
879 }
880
881
882
883 /**
884 * {@inheritDoc}
885 */
886 public boolean isConfigurationChangeAcceptable(
887 FIFOEntryCacheCfg configuration,
888 List<Message> unacceptableReasons
889 )
890 {
891 boolean applyChanges = false;
892 EntryCacheCommon.ConfigErrorHandler errorHandler =
893 EntryCacheCommon.getConfigErrorHandler (
894 EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE,
895 unacceptableReasons,
896 null
897 );
898 processEntryCacheConfig (configuration, applyChanges, errorHandler);
899
900 return errorHandler.getIsAcceptable();
901 }
902
903
904
905 /**
906 * {@inheritDoc}
907 */
908 public ConfigChangeResult applyConfigurationChange(
909 FIFOEntryCacheCfg configuration
910 )
911 {
912 boolean applyChanges = true;
913 ArrayList<Message> errorMessages = new ArrayList<Message>();
914 EntryCacheCommon.ConfigErrorHandler errorHandler =
915 EntryCacheCommon.getConfigErrorHandler (
916 EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages
917 );
918
919 // Do not apply changes unless this cache is enabled.
920 if (configuration.isEnabled()) {
921 processEntryCacheConfig (configuration, applyChanges, errorHandler);
922 }
923
924 boolean adminActionRequired = errorHandler.getIsAdminActionRequired();
925 ConfigChangeResult changeResult = new ConfigChangeResult(
926 errorHandler.getResultCode(),
927 adminActionRequired,
928 errorHandler.getErrorMessages()
929 );
930
931 return changeResult;
932 }
933
934
935
936 /**
937 * Parses the provided configuration and configure the entry cache.
938 *
939 * @param configuration The new configuration containing the changes.
940 * @param applyChanges If true then take into account the new configuration.
941 * @param errorHandler An handler used to report errors.
942 *
943 * @return <CODE>true</CODE> if configuration is acceptable,
944 * or <CODE>false</CODE> otherwise.
945 */
946 public boolean processEntryCacheConfig(
947 FIFOEntryCacheCfg configuration,
948 boolean applyChanges,
949 EntryCacheCommon.ConfigErrorHandler errorHandler
950 )
951 {
952 // Local variables to read configuration.
953 DN newConfigEntryDN;
954 long newLockTimeout;
955 long newMaxEntries;
956 int newMaxMemoryPercent;
957 long newMaxAllowedMemory;
958 HashSet<SearchFilter> newIncludeFilters = null;
959 HashSet<SearchFilter> newExcludeFilters = null;
960
961 // Read configuration.
962 newConfigEntryDN = configuration.dn();
963 newLockTimeout = configuration.getLockTimeout();
964 newMaxEntries = configuration.getMaxEntries();
965
966 // Maximum memory the cache can use.
967 newMaxMemoryPercent = configuration.getMaxMemoryPercent();
968 long maxJvmHeapSize = Runtime.getRuntime().maxMemory();
969 newMaxAllowedMemory = (maxJvmHeapSize / 100) * newMaxMemoryPercent;
970
971 // Get include and exclude filters.
972 switch (errorHandler.getConfigPhase())
973 {
974 case PHASE_INIT:
975 case PHASE_ACCEPTABLE:
976 case PHASE_APPLY:
977 newIncludeFilters = EntryCacheCommon.getFilters (
978 configuration.getIncludeFilter(),
979 ERR_CACHE_INVALID_INCLUDE_FILTER,
980 errorHandler,
981 newConfigEntryDN
982 );
983 newExcludeFilters = EntryCacheCommon.getFilters (
984 configuration.getExcludeFilter(),
985 ERR_CACHE_INVALID_EXCLUDE_FILTER,
986 errorHandler,
987 newConfigEntryDN
988 );
989 break;
990 }
991
992 if (applyChanges && errorHandler.getIsAcceptable())
993 {
994 maxEntries = newMaxEntries;
995 maxAllowedMemory = newMaxAllowedMemory;
996
997 setLockTimeout(newLockTimeout);
998 setIncludeFilters(newIncludeFilters);
999 setExcludeFilters(newExcludeFilters);
1000
1001 registeredConfiguration = configuration;
1002 }
1003
1004 return errorHandler.getIsAcceptable();
1005 }
1006
1007
1008
1009 /**
1010 * {@inheritDoc}
1011 */
1012 public ArrayList<Attribute> getMonitorData()
1013 {
1014 ArrayList<Attribute> attrs = new ArrayList<Attribute>();
1015
1016 try {
1017 attrs = EntryCacheCommon.getGenericMonitorData(
1018 new Long(cacheHits.longValue()),
1019 // If cache misses is maintained by default cache
1020 // get it from there and if not point to itself.
1021 DirectoryServer.getEntryCache().getCacheMisses(),
1022 null,
1023 new Long(maxAllowedMemory),
1024 new Long(dnMap.size()),
1025 (((maxEntries != Integer.MAX_VALUE) &&
1026 (maxEntries != Long.MAX_VALUE)) ?
1027 new Long(maxEntries) : new Long(0))
1028 );
1029 } catch (Exception e) {
1030 if (debugEnabled()) {
1031 TRACER.debugCaught(DebugLogLevel.ERROR, e);
1032 }
1033 }
1034
1035 return attrs;
1036 }
1037
1038
1039
1040 /**
1041 * {@inheritDoc}
1042 */
1043 public Long getCacheCount()
1044 {
1045 return new Long(dnMap.size());
1046 }
1047
1048
1049
1050 /**
1051 * Return a verbose string representation of the current cache maps.
1052 * This is useful primary for debugging and diagnostic purposes such
1053 * as in the entry cache unit tests.
1054 * @return String verbose string representation of the current cache
1055 * maps in the following format: dn:id:backend
1056 * one cache entry map representation per line
1057 * or <CODE>null</CODE> if all maps are empty.
1058 */
1059 private String toVerboseString()
1060 {
1061 String verboseString = new String();
1062 StringBuilder sb = new StringBuilder();
1063
1064 Map<DN,CacheEntry> dnMapCopy;
1065 Map<Backend,HashMap<Long,CacheEntry>> idMapCopy;
1066
1067 // Grab cache lock to prevent any modifications
1068 // to the cache maps until a snapshot is taken.
1069 cacheWriteLock.lock();
1070 try {
1071 // Examining the real maps will hold the lock and can cause map
1072 // modifications in case of any access order maps, make copies
1073 // instead.
1074 dnMapCopy = new LinkedHashMap<DN,CacheEntry>(dnMap);
1075 idMapCopy = new HashMap<Backend,HashMap<Long,CacheEntry>>(idMap);
1076 } finally {
1077 cacheWriteLock.unlock();
1078 }
1079
1080 // Check dnMap first.
1081 for (DN dn : dnMapCopy.keySet()) {
1082 sb.append(dn.toString());
1083 sb.append(":");
1084 sb.append((dnMapCopy.get(dn) != null ?
1085 Long.toString(dnMapCopy.get(dn).getEntryID()) : null));
1086 sb.append(":");
1087 sb.append((dnMapCopy.get(dn) != null ?
1088 dnMapCopy.get(dn).getBackend().getBackendID() : null));
1089 sb.append(ServerConstants.EOL);
1090 }
1091
1092 // See if there is anything on idMap that isnt reflected on
1093 // dnMap in case maps went out of sync.
1094 for (Backend backend : idMapCopy.keySet()) {
1095 for (Long id : idMapCopy.get(backend).keySet()) {
1096 if ((idMapCopy.get(backend).get(id) == null) ||
1097 !dnMapCopy.containsKey(
1098 idMapCopy.get(backend).get(id).getDN())) {
1099 sb.append((idMapCopy.get(backend).get(id) != null ?
1100 idMapCopy.get(backend).get(id).getDN().toString() : null));
1101 sb.append(":");
1102 sb.append(id.toString());
1103 sb.append(":");
1104 sb.append(backend.getBackendID());
1105 sb.append(ServerConstants.EOL);
1106 }
1107 }
1108 }
1109
1110 verboseString = sb.toString();
1111
1112 return (verboseString.length() > 0 ? verboseString : null);
1113 }
1114
1115 }
1116