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