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