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