001package org.apache.commons.jcs3.engine.control; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.ListIterator; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Objects; 032import java.util.Set; 033import java.util.concurrent.CopyOnWriteArrayList; 034import java.util.concurrent.ScheduledExecutorService; 035import java.util.concurrent.ScheduledFuture; 036import java.util.concurrent.TimeUnit; 037import java.util.concurrent.atomic.AtomicBoolean; 038import java.util.concurrent.atomic.AtomicLong; 039import java.util.stream.Collectors; 040import java.util.stream.Stream; 041 042import org.apache.commons.jcs3.access.exception.CacheException; 043import org.apache.commons.jcs3.access.exception.ObjectNotFoundException; 044import org.apache.commons.jcs3.auxiliary.AuxiliaryCache; 045import org.apache.commons.jcs3.engine.CacheStatus; 046import org.apache.commons.jcs3.engine.behavior.ICache; 047import org.apache.commons.jcs3.engine.behavior.ICacheElement; 048import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes; 049import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern; 050import org.apache.commons.jcs3.engine.behavior.IElementAttributes; 051import org.apache.commons.jcs3.engine.behavior.IRequireScheduler; 052import org.apache.commons.jcs3.engine.control.event.ElementEvent; 053import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType; 054import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent; 055import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler; 056import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue; 057import org.apache.commons.jcs3.engine.control.group.GroupId; 058import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl; 059import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher; 060import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache; 061import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache; 062import org.apache.commons.jcs3.engine.memory.shrinking.ShrinkerThread; 063import org.apache.commons.jcs3.engine.stats.CacheStats; 064import org.apache.commons.jcs3.engine.stats.StatElement; 065import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats; 066import org.apache.commons.jcs3.engine.stats.behavior.IStats; 067import org.apache.commons.jcs3.log.Log; 068import org.apache.commons.jcs3.log.LogManager; 069 070/** 071 * This is the primary hub for a single cache/region. It controls the flow of items through the 072 * cache. The auxiliary and memory caches are plugged in here. 073 * <p> 074 * This is the core of a JCS region. Hence, this simple class is the core of JCS. 075 */ 076public class CompositeCache<K, V> 077 implements ICache<K, V>, IRequireScheduler 078{ 079 /** log instance */ 080 private static final Log log = LogManager.getLog(CompositeCache.class); 081 082 /** 083 * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager 084 * should pass a shared queue in. 085 */ 086 private IElementEventQueue elementEventQ; 087 088 /** Auxiliary caches. */ 089 private CopyOnWriteArrayList<AuxiliaryCache<K, V>> auxCaches = new CopyOnWriteArrayList<>(); 090 091 /** is this alive? */ 092 private final AtomicBoolean alive; 093 094 /** Region Elemental Attributes, default. */ 095 private IElementAttributes attr; 096 097 /** Cache Attributes, for hub and memory auxiliary. */ 098 private ICompositeCacheAttributes cacheAttr; 099 100 /** How many times update was called. */ 101 private final AtomicLong updateCount; 102 103 /** How many times remove was called. */ 104 private final AtomicLong removeCount; 105 106 /** Memory cache hit count */ 107 private final AtomicLong hitCountRam; 108 109 /** Auxiliary cache hit count (number of times found in ANY auxiliary) */ 110 private final AtomicLong hitCountAux; 111 112 /** Count of misses where element was not found. */ 113 private final AtomicLong missCountNotFound; 114 115 /** Count of misses where element was expired. */ 116 private final AtomicLong missCountExpired; 117 118 /** Cache manager. */ 119 private CompositeCacheManager cacheManager; 120 121 /** 122 * The cache hub can only have one memory cache. This could be made more flexible in the future, 123 * but they are tied closely together. More than one doesn't make much sense. 124 */ 125 private IMemoryCache<K, V> memCache; 126 127 /** Key matcher used by the getMatching API */ 128 private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>(); 129 130 private ScheduledFuture<?> future; 131 132 /** 133 * Constructor for the Cache object 134 * <p> 135 * @param cattr The cache attribute 136 * @param attr The default element attributes 137 */ 138 public CompositeCache(final ICompositeCacheAttributes cattr, final IElementAttributes attr) 139 { 140 this.attr = attr; 141 this.cacheAttr = cattr; 142 this.alive = new AtomicBoolean(true); 143 this.updateCount = new AtomicLong(); 144 this.removeCount = new AtomicLong(); 145 this.hitCountRam = new AtomicLong(); 146 this.hitCountAux = new AtomicLong(); 147 this.missCountNotFound = new AtomicLong(); 148 this.missCountExpired = new AtomicLong(); 149 150 createMemoryCache(cattr); 151 152 log.info("Constructed cache with name [{0}] and cache attributes {1}", 153 cacheAttr.getCacheName(), cattr); 154 } 155 156 /** 157 * Injector for Element event queue 158 * 159 * @param queue 160 */ 161 public void setElementEventQueue(final IElementEventQueue queue) 162 { 163 this.elementEventQ = queue; 164 } 165 166 /** 167 * Injector for cache manager 168 * 169 * @param manager 170 */ 171 public void setCompositeCacheManager(final CompositeCacheManager manager) 172 { 173 this.cacheManager = manager; 174 } 175 176 /** 177 * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) 178 */ 179 @Override 180 public void setScheduledExecutorService(final ScheduledExecutorService scheduledExecutor) 181 { 182 if (cacheAttr.isUseMemoryShrinker()) 183 { 184 future = scheduledExecutor.scheduleAtFixedRate( 185 new ShrinkerThread<>(this), 0, cacheAttr.getShrinkerIntervalSeconds(), 186 TimeUnit.SECONDS); 187 } 188 189 if (memCache instanceof IRequireScheduler) 190 { 191 ((IRequireScheduler) memCache).setScheduledExecutorService(scheduledExecutor); 192 } 193 } 194 195 /** 196 * This sets the list of auxiliary caches for this region. 197 * It filters out null caches 198 * <p> 199 * @param auxCaches 200 * @since 3.1 201 */ 202 public void setAuxCaches(final List<AuxiliaryCache<K, V>> auxCaches) 203 { 204 this.auxCaches = auxCaches.stream() 205 .filter(Objects::nonNull) 206 .collect(Collectors.toCollection(CopyOnWriteArrayList::new)); 207 } 208 209 /** 210 * This sets the list of auxiliary caches for this region. 211 * <p> 212 * @param auxCaches 213 * @deprecated Use List method 214 */ 215 @Deprecated 216 public void setAuxCaches(final AuxiliaryCache<K, V>[] auxCaches) 217 { 218 setAuxCaches(Arrays.asList(auxCaches)); 219 } 220 221 /** 222 * Get the list of auxiliary caches for this region. 223 * <p> 224 * @return a list of auxiliary caches, may be empty, never null 225 * @since 3.1 226 */ 227 public List<AuxiliaryCache<K, V>> getAuxCacheList() 228 { 229 return this.auxCaches; 230 } 231 232 /** 233 * Get the list of auxiliary caches for this region. 234 * <p> 235 * @return an array of auxiliary caches, may be empty, never null 236 * @deprecated Use List method 237 */ 238 @SuppressWarnings("unchecked") // No generic arrays in Java 239 @Deprecated 240 public AuxiliaryCache<K, V>[] getAuxCaches() 241 { 242 return getAuxCacheList().toArray(new AuxiliaryCache[0]); 243 } 244 245 /** 246 * Standard update method. 247 * <p> 248 * @param ce 249 * @throws IOException 250 */ 251 @Override 252 public void update(final ICacheElement<K, V> ce) 253 throws IOException 254 { 255 update(ce, false); 256 } 257 258 /** 259 * Standard update method. 260 * <p> 261 * @param ce 262 * @throws IOException 263 */ 264 public void localUpdate(final ICacheElement<K, V> ce) 265 throws IOException 266 { 267 update(ce, true); 268 } 269 270 /** 271 * Put an item into the cache. If it is localOnly, then do no notify remote or lateral 272 * auxiliaries. 273 * <p> 274 * @param cacheElement the ICacheElement<K, V> 275 * @param localOnly Whether the operation should be restricted to local auxiliaries. 276 * @throws IOException 277 */ 278 protected void update(final ICacheElement<K, V> cacheElement, final boolean localOnly) 279 throws IOException 280 { 281 282 if (cacheElement.getKey() instanceof String 283 && cacheElement.getKey().toString().endsWith(NAME_COMPONENT_DELIMITER)) 284 { 285 throw new IllegalArgumentException("key must not end with " + NAME_COMPONENT_DELIMITER 286 + " for a put operation"); 287 } 288 if (cacheElement.getKey() instanceof GroupId) 289 { 290 throw new IllegalArgumentException("key cannot be a GroupId " + " for a put operation"); 291 } 292 293 log.debug("Updating memory cache {0}", cacheElement::getKey); 294 295 updateCount.incrementAndGet(); 296 memCache.update(cacheElement); 297 updateAuxiliaries(cacheElement, localOnly); 298 299 cacheElement.getElementAttributes().setLastAccessTimeNow(); 300 } 301 302 /** 303 * This method is responsible for updating the auxiliaries if they are present. If it is local 304 * only, any lateral and remote auxiliaries will not be updated. 305 * <p> 306 * Before updating an auxiliary it checks to see if the element attributes permit the operation. 307 * <p> 308 * Disk auxiliaries are only updated if the disk cache is not merely used as a swap. If the disk 309 * cache is merely a swap, then items will only go to disk when they overflow from memory. 310 * <p> 311 * This is called by update(cacheElement, localOnly) after it updates the memory cache. 312 * <p> 313 * This is protected to make it testable. 314 * <p> 315 * @param cacheElement 316 * @param localOnly 317 * @throws IOException 318 */ 319 protected void updateAuxiliaries(final ICacheElement<K, V> cacheElement, final boolean localOnly) 320 throws IOException 321 { 322 // UPDATE AUXILLIARY CACHES 323 // There are 3 types of auxiliary caches: remote, lateral, and disk 324 // more can be added if future auxiliary caches don't fit the model 325 // You could run a database cache as either a remote or a local disk. 326 // The types would describe the purpose. 327 if (!auxCaches.isEmpty()) 328 { 329 log.debug("Updating auxiliary caches"); 330 } 331 else 332 { 333 log.debug("No auxiliary cache to update"); 334 } 335 336 for (final ICache<K, V> aux : auxCaches) 337 { 338 if (aux == null) 339 { 340 continue; 341 } 342 343 log.debug("Auxiliary cache type: {0}", aux.getCacheType()); 344 345 switch (aux.getCacheType()) 346 { 347 // SEND TO REMOTE STORE 348 case REMOTE_CACHE: 349 log.debug("ce.getElementAttributes().getIsRemote() = {0}", 350 cacheElement.getElementAttributes()::getIsRemote); 351 352 if (cacheElement.getElementAttributes().getIsRemote() && !localOnly) 353 { 354 try 355 { 356 // need to make sure the group cache understands that 357 // the key is a group attribute on update 358 aux.update(cacheElement); 359 log.debug("Updated remote store for {0} {1}", 360 cacheElement.getKey(), cacheElement); 361 } 362 catch (final IOException ex) 363 { 364 log.error("Failure in updateExclude", ex); 365 } 366 } 367 break; 368 369 // SEND LATERALLY 370 case LATERAL_CACHE: 371 // lateral can't do the checking since it is dependent on the 372 // cache region restrictions 373 log.debug("lateralcache in aux list: cattr {0}", cacheAttr::isUseLateral); 374 if (cacheAttr.isUseLateral() && cacheElement.getElementAttributes().getIsLateral() && !localOnly) 375 { 376 // DISTRIBUTE LATERALLY 377 // Currently always multicast even if the value is 378 // unchanged, to cause the cache item to move to the front. 379 aux.update(cacheElement); 380 log.debug("updated lateral cache for {0}", cacheElement::getKey); 381 } 382 break; 383 384 // update disk if the usage pattern permits 385 case DISK_CACHE: 386 log.debug("diskcache in aux list: cattr {0}", cacheAttr::isUseDisk); 387 if (cacheAttr.isUseDisk() 388 && cacheAttr.getDiskUsagePattern() == DiskUsagePattern.UPDATE 389 && cacheElement.getElementAttributes().getIsSpool()) 390 { 391 aux.update(cacheElement); 392 log.debug("updated disk cache for {0}", cacheElement::getKey); 393 } 394 break; 395 396 default: // CACHE_HUB 397 break; 398 } 399 } 400 } 401 402 /** 403 * Writes the specified element to any disk auxiliaries. Might want to rename this "overflow" in 404 * case the hub wants to do something else. 405 * <p> 406 * If JCS is not configured to use the disk as a swap, that is if the 407 * CompositeCacheAttribute diskUsagePattern is not SWAP_ONLY, then the item will not be spooled. 408 * <p> 409 * @param ce The CacheElement 410 */ 411 public void spoolToDisk(final ICacheElement<K, V> ce) 412 { 413 // if the item is not spoolable, return 414 if (!ce.getElementAttributes().getIsSpool()) 415 { 416 // there is an event defined for this. 417 handleElementEvent(ce, ElementEventType.SPOOLED_NOT_ALLOWED); 418 return; 419 } 420 421 boolean diskAvailable = false; 422 423 // SPOOL TO DISK. 424 for (final ICache<K, V> aux : auxCaches) 425 { 426 if (aux.getCacheType() == CacheType.DISK_CACHE) 427 { 428 diskAvailable = true; 429 430 if (cacheAttr.getDiskUsagePattern() == DiskUsagePattern.SWAP) 431 { 432 // write the last items to disk.2 433 try 434 { 435 handleElementEvent(ce, ElementEventType.SPOOLED_DISK_AVAILABLE); 436 aux.update(ce); 437 } 438 catch (final IOException ex) 439 { 440 // impossible case. 441 log.error("Problem spooling item to disk cache.", ex); 442 throw new IllegalStateException(ex.getMessage()); 443 } 444 445 log.debug("spoolToDisk done for: {0} on disk cache[{1}]", 446 ce::getKey, aux::getCacheName); 447 } 448 else 449 { 450 log.debug("DiskCache available, but JCS is not configured " 451 + "to use the DiskCache as a swap."); 452 } 453 } 454 } 455 456 if (!diskAvailable) 457 { 458 handleElementEvent(ce, ElementEventType.SPOOLED_DISK_NOT_AVAILABLE); 459 } 460 } 461 462 /** 463 * Gets an item from the cache. 464 * <p> 465 * @param key 466 * @return element from the cache, or null if not present 467 * @see org.apache.commons.jcs3.engine.behavior.ICache#get(Object) 468 */ 469 @Override 470 public ICacheElement<K, V> get(final K key) 471 { 472 return get(key, false); 473 } 474 475 /** 476 * Do not try to go remote or laterally for this get. 477 * <p> 478 * @param key 479 * @return ICacheElement 480 */ 481 public ICacheElement<K, V> localGet(final K key) 482 { 483 return get(key, true); 484 } 485 486 /** 487 * Look in memory, then disk, remote, or laterally for this item. The order is dependent on the 488 * order in the cache.ccf file. 489 * <p> 490 * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go 491 * remote or lateral if such an auxiliary is configured for this region. 492 * <p> 493 * @param key 494 * @param localOnly 495 * @return ICacheElement 496 */ 497 protected ICacheElement<K, V> get(final K key, final boolean localOnly) 498 { 499 ICacheElement<K, V> element = null; 500 501 boolean found = false; 502 503 log.debug("get: key = {0}, localOnly = {1}", key, localOnly); 504 505 try 506 { 507 // First look in memory cache 508 element = memCache.get(key); 509 510 if (element != null) 511 { 512 // Found in memory cache 513 if (isExpired(element)) 514 { 515 log.debug("{0} - Memory cache hit, but element expired", 516 () -> cacheAttr.getCacheName()); 517 518 doExpires(element); 519 element = null; 520 } 521 else 522 { 523 log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName()); 524 525 // Update counters 526 hitCountRam.incrementAndGet(); 527 } 528 529 found = true; 530 } 531 else 532 { 533 // Item not found in memory. If local invocation look in aux 534 // caches, even if not local look in disk auxiliaries 535 for (final AuxiliaryCache<K, V> aux : auxCaches) 536 { 537 final CacheType cacheType = aux.getCacheType(); 538 539 if (!localOnly || cacheType == CacheType.DISK_CACHE) 540 { 541 log.debug("Attempting to get from aux [{0}] which is of type: {1}", 542 aux::getCacheName, () -> cacheType); 543 544 try 545 { 546 element = aux.get(key); 547 } 548 catch (final IOException e) 549 { 550 log.error("Error getting from aux", e); 551 } 552 } 553 554 log.debug("Got CacheElement: {0}", element); 555 556 // Item found in one of the auxiliary caches. 557 if (element != null) 558 { 559 if (isExpired(element)) 560 { 561 log.debug("{0} - Aux cache[{1}] hit, but element expired.", 562 () -> cacheAttr.getCacheName(), aux::getCacheName); 563 564 // This will tell the remotes to remove the item 565 // based on the element's expiration policy. The elements attributes 566 // associated with the item when it created govern its behavior 567 // everywhere. 568 doExpires(element); 569 element = null; 570 } 571 else 572 { 573 log.debug("{0} - Aux cache[{1}] hit.", 574 () -> cacheAttr.getCacheName(), aux::getCacheName); 575 576 // Update counters 577 hitCountAux.incrementAndGet(); 578 copyAuxiliaryRetrievedItemToMemory(element); 579 } 580 581 found = true; 582 583 break; 584 } 585 } 586 } 587 } 588 catch (final IOException e) 589 { 590 log.error("Problem encountered getting element.", e); 591 } 592 593 if (!found) 594 { 595 missCountNotFound.incrementAndGet(); 596 597 log.debug("{0} - Miss", () -> cacheAttr.getCacheName()); 598 } 599 600 if (element != null) 601 { 602 element.getElementAttributes().setLastAccessTimeNow(); 603 } 604 605 return element; 606 } 607 608 protected void doExpires(final ICacheElement<K, V> element) 609 { 610 missCountExpired.incrementAndGet(); 611 remove(element.getKey()); 612 } 613 614 /** 615 * Gets multiple items from the cache based on the given set of keys. 616 * <p> 617 * @param keys 618 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 619 * data in cache for any of these keys 620 */ 621 @Override 622 public Map<K, ICacheElement<K, V>> getMultiple(final Set<K> keys) 623 { 624 return getMultiple(keys, false); 625 } 626 627 /** 628 * Gets multiple items from the cache based on the given set of keys. Do not try to go remote or 629 * laterally for this data. 630 * <p> 631 * @param keys 632 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 633 * data in cache for any of these keys 634 */ 635 public Map<K, ICacheElement<K, V>> localGetMultiple(final Set<K> keys) 636 { 637 return getMultiple(keys, true); 638 } 639 640 /** 641 * Look in memory, then disk, remote, or laterally for these items. The order is dependent on 642 * the order in the cache.ccf file. Keep looking in each cache location until either the element 643 * is found, or the method runs out of places to look. 644 * <p> 645 * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go 646 * remote or lateral if such an auxiliary is configured for this region. 647 * <p> 648 * @param keys 649 * @param localOnly 650 * @return ICacheElement 651 */ 652 protected Map<K, ICacheElement<K, V>> getMultiple(final Set<K> keys, final boolean localOnly) 653 { 654 final Map<K, ICacheElement<K, V>> elements = new HashMap<>(); 655 656 log.debug("get: key = {0}, localOnly = {1}", keys, localOnly); 657 658 try 659 { 660 // First look in memory cache 661 elements.putAll(getMultipleFromMemory(keys)); 662 663 // If fewer than all items were found in memory, then keep looking. 664 if (elements.size() != keys.size()) 665 { 666 final Set<K> remainingKeys = pruneKeysFound(keys, elements); 667 elements.putAll(getMultipleFromAuxiliaryCaches(remainingKeys, localOnly)); 668 } 669 } 670 catch (final IOException e) 671 { 672 log.error("Problem encountered getting elements.", e); 673 } 674 675 // if we didn't find all the elements, increment the miss count by the number of elements not found 676 if (elements.size() != keys.size()) 677 { 678 missCountNotFound.addAndGet(keys.size() - elements.size()); 679 680 log.debug("{0} - {1} Misses", () -> cacheAttr.getCacheName(), 681 () -> keys.size() - elements.size()); 682 } 683 684 return elements; 685 } 686 687 /** 688 * Gets items for the keys in the set. Returns a map: key -> result. 689 * <p> 690 * @param keys 691 * @return the elements found in the memory cache 692 * @throws IOException 693 */ 694 private Map<K, ICacheElement<K, V>> getMultipleFromMemory(final Set<K> keys) 695 throws IOException 696 { 697 final Map<K, ICacheElement<K, V>> elementsFromMemory = memCache.getMultiple(keys); 698 elementsFromMemory.entrySet().removeIf(entry -> { 699 final ICacheElement<K, V> element = entry.getValue(); 700 if (isExpired(element)) 701 { 702 log.debug("{0} - Memory cache hit, but element expired", 703 () -> cacheAttr.getCacheName()); 704 705 doExpires(element); 706 return true; 707 } 708 log.debug("{0} - Memory cache hit", () -> cacheAttr.getCacheName()); 709 710 // Update counters 711 hitCountRam.incrementAndGet(); 712 return false; 713 }); 714 715 return elementsFromMemory; 716 } 717 718 /** 719 * If local invocation look in aux caches, even if not local look in disk auxiliaries. 720 * <p> 721 * @param keys 722 * @param localOnly 723 * @return the elements found in the auxiliary caches 724 * @throws IOException 725 */ 726 private Map<K, ICacheElement<K, V>> getMultipleFromAuxiliaryCaches(final Set<K> keys, final boolean localOnly) 727 throws IOException 728 { 729 final Map<K, ICacheElement<K, V>> elements = new HashMap<>(); 730 Set<K> remainingKeys = new HashSet<>(keys); 731 732 for (final AuxiliaryCache<K, V> aux : auxCaches) 733 { 734 final Map<K, ICacheElement<K, V>> elementsFromAuxiliary = 735 new HashMap<>(); 736 737 final CacheType cacheType = aux.getCacheType(); 738 739 if (!localOnly || cacheType == CacheType.DISK_CACHE) 740 { 741 log.debug("Attempting to get from aux [{0}] which is of type: {1}", 742 aux::getCacheName, () -> cacheType); 743 744 try 745 { 746 elementsFromAuxiliary.putAll(aux.getMultiple(remainingKeys)); 747 } 748 catch (final IOException e) 749 { 750 log.error("Error getting from aux", e); 751 } 752 } 753 754 log.debug("Got CacheElements: {0}", elementsFromAuxiliary); 755 756 processRetrievedElements(aux, elementsFromAuxiliary); 757 elements.putAll(elementsFromAuxiliary); 758 759 if (elements.size() == keys.size()) 760 { 761 break; 762 } 763 remainingKeys = pruneKeysFound(keys, elements); 764 } 765 766 return elements; 767 } 768 769 /** 770 * Build a map of all the matching elements in all of the auxiliaries and memory. 771 * <p> 772 * @param pattern 773 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 774 * data in cache for any matching keys 775 */ 776 @Override 777 public Map<K, ICacheElement<K, V>> getMatching(final String pattern) 778 { 779 return getMatching(pattern, false); 780 } 781 782 /** 783 * Build a map of all the matching elements in all of the auxiliaries and memory. Do not try to 784 * go remote or laterally for this data. 785 * <p> 786 * @param pattern 787 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 788 * data in cache for any matching keys 789 */ 790 public Map<K, ICacheElement<K, V>> localGetMatching(final String pattern) 791 { 792 return getMatching(pattern, true); 793 } 794 795 /** 796 * Build a map of all the matching elements in all of the auxiliaries and memory. Items in 797 * memory will replace from the auxiliaries in the returned map. The auxiliaries are accessed in 798 * opposite order. It's assumed that those closer to home are better. 799 * <p> 800 * Do not try to go remote or laterally for this get if it is localOnly. Otherwise try to go 801 * remote or lateral if such an auxiliary is configured for this region. 802 * <p> 803 * @param pattern 804 * @param localOnly 805 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 806 * data in cache for any matching keys 807 */ 808 protected Map<K, ICacheElement<K, V>> getMatching(final String pattern, final boolean localOnly) 809 { 810 log.debug("get: pattern [{0}], localOnly = {1}", pattern, localOnly); 811 812 try 813 { 814 return Stream.concat( 815 getMatchingFromMemory(pattern).entrySet().stream(), 816 getMatchingFromAuxiliaryCaches(pattern, localOnly).entrySet().stream()) 817 .collect(Collectors.toMap( 818 Entry::getKey, 819 Entry::getValue, 820 // Prefer memory entries 821 (mem, aux) -> mem)); 822 } 823 catch (final IOException e) 824 { 825 log.error("Problem encountered getting elements.", e); 826 } 827 828 return new HashMap<>(); 829 } 830 831 /** 832 * Gets the key array from the memcache. Builds a set of matches. Calls getMultiple with the 833 * set. Returns a map: key -> result. 834 * <p> 835 * @param pattern 836 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 837 * data in cache for any matching keys 838 * @throws IOException 839 */ 840 protected Map<K, ICacheElement<K, V>> getMatchingFromMemory(final String pattern) 841 throws IOException 842 { 843 // find matches in key array 844 // this avoids locking the memory cache, but it uses more memory 845 final Set<K> keyArray = memCache.getKeySet(); 846 final Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray(pattern, keyArray); 847 848 // call get multiple 849 return getMultipleFromMemory(matchingKeys); 850 } 851 852 /** 853 * If local invocation look in aux caches, even if not local look in disk auxiliaries. 854 * <p> 855 * Moves in reverse order of definition. This will allow you to override those that are from the 856 * remote with those on disk. 857 * <p> 858 * @param pattern 859 * @param localOnly 860 * @return a map of K key to ICacheElement<K, V> element, or an empty map if there is no 861 * data in cache for any matching keys 862 * @throws IOException 863 */ 864 private Map<K, ICacheElement<K, V>> getMatchingFromAuxiliaryCaches(final String pattern, final boolean localOnly) 865 throws IOException 866 { 867 final Map<K, ICacheElement<K, V>> elements = new HashMap<>(); 868 869 for (ListIterator<AuxiliaryCache<K, V>> i = auxCaches.listIterator(auxCaches.size()); i.hasPrevious();) 870 { 871 final AuxiliaryCache<K, V> aux = i.previous(); 872 873 final Map<K, ICacheElement<K, V>> elementsFromAuxiliary = 874 new HashMap<>(); 875 876 final CacheType cacheType = aux.getCacheType(); 877 878 if (!localOnly || cacheType == CacheType.DISK_CACHE) 879 { 880 log.debug("Attempting to get from aux [{0}] which is of type: {1}", 881 aux::getCacheName, () -> cacheType); 882 883 try 884 { 885 elementsFromAuxiliary.putAll(aux.getMatching(pattern)); 886 } 887 catch (final IOException e) 888 { 889 log.error("Error getting from aux", e); 890 } 891 892 log.debug("Got CacheElements: {0}", elementsFromAuxiliary); 893 894 processRetrievedElements(aux, elementsFromAuxiliary); 895 elements.putAll(elementsFromAuxiliary); 896 } 897 } 898 899 return elements; 900 } 901 902 /** 903 * Remove expired elements retrieved from an auxiliary. Update memory with good items. 904 * <p> 905 * @param aux the auxiliary cache instance 906 * @param elementsFromAuxiliary 907 * @throws IOException 908 */ 909 private void processRetrievedElements(final AuxiliaryCache<K, V> aux, final Map<K, ICacheElement<K, V>> elementsFromAuxiliary) 910 throws IOException 911 { 912 elementsFromAuxiliary.entrySet().removeIf(entry -> { 913 final ICacheElement<K, V> element = entry.getValue(); 914 915 // Item found in one of the auxiliary caches. 916 if (element != null) 917 { 918 if (isExpired(element)) 919 { 920 log.debug("{0} - Aux cache[{1}] hit, but element expired.", 921 () -> cacheAttr.getCacheName(), aux::getCacheName); 922 923 // This will tell the remote caches to remove the item 924 // based on the element's expiration policy. The elements attributes 925 // associated with the item when it created govern its behavior 926 // everywhere. 927 doExpires(element); 928 return true; 929 } 930 log.debug("{0} - Aux cache[{1}] hit.", 931 () -> cacheAttr.getCacheName(), aux::getCacheName); 932 933 // Update counters 934 hitCountAux.incrementAndGet(); 935 try 936 { 937 copyAuxiliaryRetrievedItemToMemory(element); 938 } 939 catch (final IOException e) 940 { 941 log.error("{0} failed to copy element to memory {1}", 942 cacheAttr.getCacheName(), element, e); 943 } 944 } 945 946 return false; 947 }); 948 } 949 950 /** 951 * Copies the item to memory if the memory size is greater than 0. Only spool if the memory 952 * cache size is greater than 0, else the item will immediately get put into purgatory. 953 * <p> 954 * @param element 955 * @throws IOException 956 */ 957 private void copyAuxiliaryRetrievedItemToMemory(final ICacheElement<K, V> element) 958 throws IOException 959 { 960 if (memCache.getCacheAttributes().getMaxObjects() > 0) 961 { 962 memCache.update(element); 963 } 964 else 965 { 966 log.debug("Skipping memory update since no items are allowed in memory"); 967 } 968 } 969 970 /** 971 * Returns a set of keys that were not found. 972 * <p> 973 * @param keys 974 * @param foundElements 975 * @return the original set of cache keys, minus any cache keys present in the map keys of the 976 * foundElements map 977 */ 978 private Set<K> pruneKeysFound(final Set<K> keys, final Map<K, ICacheElement<K, V>> foundElements) 979 { 980 final Set<K> remainingKeys = new HashSet<>(keys); 981 remainingKeys.removeAll(foundElements.keySet()); 982 983 return remainingKeys; 984 } 985 986 /** 987 * Get a set of the keys for all elements in the cache 988 * <p> 989 * @return A set of the key type 990 */ 991 public Set<K> getKeySet() 992 { 993 return getKeySet(false); 994 } 995 996 /** 997 * Get a set of the keys for all elements in the cache 998 * <p> 999 * @param localOnly true if only memory keys are requested 1000 * 1001 * @return A set of the key type 1002 */ 1003 public Set<K> getKeySet(final boolean localOnly) 1004 { 1005 return Stream.concat(memCache.getKeySet().stream(), auxCaches.stream() 1006 .filter(aux -> !localOnly || aux.getCacheType() == CacheType.DISK_CACHE) 1007 .flatMap(aux -> { 1008 try 1009 { 1010 return aux.getKeySet().stream(); 1011 } 1012 catch (final IOException e) 1013 { 1014 return Stream.of(); 1015 } 1016 })) 1017 .collect(Collectors.toSet()); 1018 } 1019 1020 /** 1021 * Removes an item from the cache. 1022 * <p> 1023 * @param key 1024 * @return true is it was removed 1025 * @see org.apache.commons.jcs3.engine.behavior.ICache#remove(Object) 1026 */ 1027 @Override 1028 public boolean remove(final K key) 1029 { 1030 return remove(key, false); 1031 } 1032 1033 /** 1034 * Do not propagate removeall laterally or remotely. 1035 * <p> 1036 * @param key 1037 * @return true if the item was already in the cache. 1038 */ 1039 public boolean localRemove(final K key) 1040 { 1041 return remove(key, true); 1042 } 1043 1044 /** 1045 * fromRemote: If a remove call was made on a cache with both, then the remote should have been 1046 * called. If it wasn't then the remote is down. we'll assume it is down for all. If it did come 1047 * from the remote then the cache is remotely configured and lateral removal is unnecessary. If 1048 * it came laterally then lateral removal is unnecessary. Does this assume that there is only 1049 * one lateral and remote for the cache? Not really, the initial removal should take care of the 1050 * problem if the source cache was similarly configured. Otherwise the remote cache, if it had 1051 * no laterals, would remove all the elements from remotely configured caches, but if those 1052 * caches had some other weird laterals that were not remotely configured, only laterally 1053 * propagated then they would go out of synch. The same could happen for multiple remotes. If 1054 * this looks necessary we will need to build in an identifier to specify the source of a 1055 * removal. 1056 * <p> 1057 * @param key 1058 * @param localOnly 1059 * @return true if the item was in the cache, else false 1060 */ 1061 protected boolean remove(final K key, final boolean localOnly) 1062 { 1063 removeCount.incrementAndGet(); 1064 1065 boolean removed = false; 1066 1067 try 1068 { 1069 removed = memCache.remove(key); 1070 } 1071 catch (final IOException e) 1072 { 1073 log.error(e); 1074 } 1075 1076 // Removes from all auxiliary caches. 1077 for (final ICache<K, V> aux : auxCaches) 1078 { 1079 if (aux == null) 1080 { 1081 continue; 1082 } 1083 1084 final CacheType cacheType = aux.getCacheType(); 1085 1086 // for now let laterals call remote remove but not vice versa 1087 if (localOnly && (cacheType == CacheType.REMOTE_CACHE || cacheType == CacheType.LATERAL_CACHE)) 1088 { 1089 continue; 1090 } 1091 try 1092 { 1093 log.debug("Removing {0} from cacheType {1}", key, cacheType); 1094 1095 final boolean b = aux.remove(key); 1096 1097 // Don't take the remote removal into account. 1098 if (!removed && cacheType != CacheType.REMOTE_CACHE) 1099 { 1100 removed = b; 1101 } 1102 } 1103 catch (final IOException ex) 1104 { 1105 log.error("Failure removing from aux", ex); 1106 } 1107 } 1108 1109 return removed; 1110 } 1111 1112 /** 1113 * Clears the region. This command will be sent to all auxiliaries. Some auxiliaries, such as 1114 * the JDBC disk cache, can be configured to not honor removeAll requests. 1115 * <p> 1116 * @see org.apache.commons.jcs3.engine.behavior.ICache#removeAll() 1117 */ 1118 @Override 1119 public void removeAll() 1120 throws IOException 1121 { 1122 removeAll(false); 1123 } 1124 1125 /** 1126 * Will not pass the remove message remotely. 1127 * <p> 1128 * @throws IOException 1129 */ 1130 public void localRemoveAll() 1131 throws IOException 1132 { 1133 removeAll(true); 1134 } 1135 1136 /** 1137 * Removes all cached items. 1138 * <p> 1139 * @param localOnly must pass in false to get remote and lateral aux's updated. This prevents 1140 * looping. 1141 * @throws IOException 1142 */ 1143 protected void removeAll(final boolean localOnly) 1144 throws IOException 1145 { 1146 try 1147 { 1148 memCache.removeAll(); 1149 1150 log.debug("Removed All keys from the memory cache."); 1151 } 1152 catch (final IOException ex) 1153 { 1154 log.error("Trouble updating memory cache.", ex); 1155 } 1156 1157 // Removes from all auxiliary disk caches. 1158 auxCaches.stream() 1159 .filter(aux -> aux.getCacheType() == CacheType.DISK_CACHE || !localOnly) 1160 .forEach(aux -> { 1161 try 1162 { 1163 log.debug("Removing All keys from cacheType {0}", 1164 aux::getCacheType); 1165 1166 aux.removeAll(); 1167 } 1168 catch (final IOException ex) 1169 { 1170 log.error("Failure removing all from aux " + aux, ex); 1171 } 1172 }); 1173 } 1174 1175 /** 1176 * Flushes all cache items from memory to auxiliary caches and close the auxiliary caches. 1177 */ 1178 @Override 1179 public void dispose() 1180 { 1181 dispose(false); 1182 } 1183 1184 /** 1185 * Invoked only by CacheManager. This method disposes of the auxiliaries one by one. For the 1186 * disk cache, the items in memory are freed, meaning that they will be sent through the 1187 * overflow channel to disk. After the auxiliaries are disposed, the memory cache is disposed. 1188 * <p> 1189 * @param fromRemote 1190 */ 1191 public void dispose(final boolean fromRemote) 1192 { 1193 // If already disposed, return immediately 1194 if (!alive.compareAndSet(true, false)) 1195 { 1196 return; 1197 } 1198 1199 log.info("In DISPOSE, [{0}] fromRemote [{1}]", 1200 this.cacheAttr::getCacheName, () -> fromRemote); 1201 1202 // Remove us from the cache managers list 1203 // This will call us back but exit immediately 1204 if (cacheManager != null) 1205 { 1206 cacheManager.freeCache(getCacheName(), fromRemote); 1207 } 1208 1209 // Try to stop shrinker thread 1210 if (future != null) 1211 { 1212 future.cancel(true); 1213 } 1214 1215 // Now, shut down the event queue 1216 if (elementEventQ != null) 1217 { 1218 elementEventQ.dispose(); 1219 elementEventQ = null; 1220 } 1221 1222 // Dispose of each auxiliary cache, Remote auxiliaries will be 1223 // skipped if 'fromRemote' is true. 1224 for (final ICache<K, V> aux : auxCaches) 1225 { 1226 try 1227 { 1228 // Skip this auxiliary if: 1229 // - The auxiliary is null 1230 // - The auxiliary is not alive 1231 // - The auxiliary is remote and the invocation was remote 1232 if (aux == null || aux.getStatus() != CacheStatus.ALIVE 1233 || fromRemote && aux.getCacheType() == CacheType.REMOTE_CACHE) 1234 { 1235 log.info("In DISPOSE, [{0}] SKIPPING auxiliary [{1}] fromRemote [{2}]", 1236 this.cacheAttr::getCacheName, 1237 () -> aux == null ? "null" : aux.getCacheName(), 1238 () -> fromRemote); 1239 continue; 1240 } 1241 1242 log.info("In DISPOSE, [{0}] auxiliary [{1}]", 1243 this.cacheAttr::getCacheName, aux::getCacheName); 1244 1245 // IT USED TO BE THE CASE THAT (If the auxiliary is not a lateral, or the cache 1246 // attributes 1247 // have 'getUseLateral' set, all the elements currently in 1248 // memory are written to the lateral before disposing) 1249 // I changed this. It was excessive. Only the disk cache needs the items, since only 1250 // the disk cache is in a situation to not get items on a put. 1251 if (aux.getCacheType() == CacheType.DISK_CACHE) 1252 { 1253 final int numToFree = memCache.getSize(); 1254 memCache.freeElements(numToFree); 1255 1256 log.info("In DISPOSE, [{0}] put {1} into auxiliary [{2}]", 1257 this.cacheAttr::getCacheName, () -> numToFree, 1258 aux::getCacheName); 1259 } 1260 1261 // Dispose of the auxiliary 1262 aux.dispose(); 1263 } 1264 catch (final IOException ex) 1265 { 1266 log.error("Failure disposing of aux.", ex); 1267 } 1268 } 1269 1270 log.info("In DISPOSE, [{0}] disposing of memory cache.", 1271 this.cacheAttr::getCacheName); 1272 try 1273 { 1274 memCache.dispose(); 1275 } 1276 catch (final IOException ex) 1277 { 1278 log.error("Failure disposing of memCache", ex); 1279 } 1280 } 1281 1282 /** 1283 * Calling save cause the entire contents of the memory cache to be flushed to all auxiliaries. 1284 * Though this put is extremely fast, this could bog the cache and should be avoided. The 1285 * dispose method should call a version of this. Good for testing. 1286 */ 1287 public void save() 1288 { 1289 if (!alive.get()) 1290 { 1291 return; 1292 } 1293 1294 auxCaches.stream() 1295 .filter(aux -> aux.getStatus() == CacheStatus.ALIVE) 1296 .forEach(aux -> { 1297 memCache.getKeySet().stream() 1298 .map(this::localGet) 1299 .filter(Objects::nonNull) 1300 .forEach(ce -> { 1301 try 1302 { 1303 aux.update(ce); 1304 } 1305 catch (IOException e) 1306 { 1307 log.warn("Failure saving element {0} to aux {1}.", ce, aux, e); 1308 } 1309 }); 1310 }); 1311 1312 log.debug("Called save for [{0}]", cacheAttr::getCacheName); 1313 } 1314 1315 /** 1316 * Gets the size attribute of the Cache object. This return the number of elements, not the byte 1317 * size. 1318 * <p> 1319 * @return The size value 1320 */ 1321 @Override 1322 public int getSize() 1323 { 1324 return memCache.getSize(); 1325 } 1326 1327 /** 1328 * Gets the cacheType attribute of the Cache object. 1329 * <p> 1330 * @return The cacheType value 1331 */ 1332 @Override 1333 public CacheType getCacheType() 1334 { 1335 return CacheType.CACHE_HUB; 1336 } 1337 1338 /** 1339 * Gets the status attribute of the Cache object. 1340 * <p> 1341 * @return The status value 1342 */ 1343 @Override 1344 public CacheStatus getStatus() 1345 { 1346 return alive.get() ? CacheStatus.ALIVE : CacheStatus.DISPOSED; 1347 } 1348 1349 /** 1350 * Gets stats for debugging. 1351 * <p> 1352 * @return String 1353 */ 1354 @Override 1355 public String getStats() 1356 { 1357 return getStatistics().toString(); 1358 } 1359 1360 /** 1361 * This returns data gathered for this region and all the auxiliaries it currently uses. 1362 * <p> 1363 * @return Statistics and Info on the Region. 1364 */ 1365 public ICacheStats getStatistics() 1366 { 1367 final ICacheStats stats = new CacheStats(); 1368 stats.setRegionName(this.getCacheName()); 1369 1370 // store the composite cache stats first 1371 stats.setStatElements(Arrays.asList( 1372 new StatElement<>("HitCountRam", Long.valueOf(getHitCountRam())), 1373 new StatElement<>("HitCountAux", Long.valueOf(getHitCountAux())))); 1374 1375 // memory + aux, memory is not considered an auxiliary internally 1376 final ArrayList<IStats> auxStats = new ArrayList<>(auxCaches.size() + 1); 1377 1378 auxStats.add(getMemoryCache().getStatistics()); 1379 auxStats.addAll(auxCaches.stream() 1380 .map(AuxiliaryCache::getStatistics) 1381 .collect(Collectors.toList())); 1382 1383 // store the auxiliary stats 1384 stats.setAuxiliaryCacheStats(auxStats); 1385 1386 return stats; 1387 } 1388 1389 /** 1390 * Gets the cacheName attribute of the Cache object. This is also known as the region name. 1391 * <p> 1392 * @return The cacheName value 1393 */ 1394 @Override 1395 public String getCacheName() 1396 { 1397 return cacheAttr.getCacheName(); 1398 } 1399 1400 /** 1401 * Gets the default element attribute of the Cache object This returns a copy. It does not 1402 * return a reference to the attributes. 1403 * <p> 1404 * @return The attributes value 1405 */ 1406 public IElementAttributes getElementAttributes() 1407 { 1408 if (attr != null) 1409 { 1410 return attr.clone(); 1411 } 1412 return null; 1413 } 1414 1415 /** 1416 * Sets the default element attribute of the Cache object. 1417 * <p> 1418 * @param attr 1419 */ 1420 public void setElementAttributes(final IElementAttributes attr) 1421 { 1422 this.attr = attr; 1423 } 1424 1425 /** 1426 * Gets the ICompositeCacheAttributes attribute of the Cache object. 1427 * <p> 1428 * @return The ICompositeCacheAttributes value 1429 */ 1430 public ICompositeCacheAttributes getCacheAttributes() 1431 { 1432 return this.cacheAttr; 1433 } 1434 1435 /** 1436 * Sets the ICompositeCacheAttributes attribute of the Cache object. 1437 * <p> 1438 * @param cattr The new ICompositeCacheAttributes value 1439 */ 1440 public void setCacheAttributes(final ICompositeCacheAttributes cattr) 1441 { 1442 this.cacheAttr = cattr; 1443 // need a better way to do this, what if it is in error 1444 this.memCache.initialize(this); 1445 } 1446 1447 /** 1448 * Gets the elementAttributes attribute of the Cache object. 1449 * <p> 1450 * @param key 1451 * @return The elementAttributes value 1452 * @throws CacheException 1453 * @throws IOException 1454 */ 1455 public IElementAttributes getElementAttributes(final K key) 1456 throws CacheException, IOException 1457 { 1458 final ICacheElement<K, V> ce = get(key); 1459 if (ce == null) 1460 { 1461 throw new ObjectNotFoundException("key " + key + " is not found"); 1462 } 1463 return ce.getElementAttributes(); 1464 } 1465 1466 /** 1467 * Determine if the element is expired based on the values of the element attributes 1468 * 1469 * @param element the element 1470 * 1471 * @return true if the element is expired 1472 */ 1473 public boolean isExpired(final ICacheElement<K, V> element) 1474 { 1475 return isExpired(element, System.currentTimeMillis(), 1476 ElementEventType.EXCEEDED_MAXLIFE_ONREQUEST, 1477 ElementEventType.EXCEEDED_IDLETIME_ONREQUEST); 1478 } 1479 1480 /** 1481 * Check if the element is expired based on the values of the element attributes 1482 * 1483 * @param element the element 1484 * @param timestamp the timestamp to compare to 1485 * @param eventMaxlife the event to fire in case the max life time is exceeded 1486 * @param eventIdle the event to fire in case the idle time is exceeded 1487 * 1488 * @return true if the element is expired 1489 */ 1490 public boolean isExpired(final ICacheElement<K, V> element, final long timestamp, 1491 final ElementEventType eventMaxlife, final ElementEventType eventIdle) 1492 { 1493 try 1494 { 1495 final IElementAttributes attributes = element.getElementAttributes(); 1496 1497 if (!attributes.getIsEternal()) 1498 { 1499 // Remove if maxLifeSeconds exceeded 1500 final long maxLifeSeconds = attributes.getMaxLife(); 1501 final long createTime = attributes.getCreateTime(); 1502 1503 final long timeFactorForMilliseconds = attributes.getTimeFactorForMilliseconds(); 1504 1505 if (maxLifeSeconds != -1 && (timestamp - createTime) > (maxLifeSeconds * timeFactorForMilliseconds)) 1506 { 1507 log.debug("Exceeded maxLife: {0}", element::getKey); 1508 1509 handleElementEvent(element, eventMaxlife); 1510 return true; 1511 } 1512 final long idleTime = attributes.getIdleTime(); 1513 final long lastAccessTime = attributes.getLastAccessTime(); 1514 1515 // Remove if maxIdleTime exceeded 1516 // If you have a 0 size memory cache, then the last access will 1517 // not get updated. 1518 // you will need to set the idle time to -1. 1519 if (idleTime != -1 && timestamp - lastAccessTime > idleTime * timeFactorForMilliseconds) 1520 { 1521 log.debug("Exceeded maxIdle: {0}", element::getKey); 1522 1523 handleElementEvent(element, eventIdle); 1524 return true; 1525 } 1526 } 1527 } 1528 catch (final Exception e) 1529 { 1530 log.error("Error determining expiration period, expiring", e); 1531 return true; 1532 } 1533 1534 return false; 1535 } 1536 1537 /** 1538 * If there are event handlers for the item, then create an event and queue it up. 1539 * <p> 1540 * This does not call handle directly; instead the handler and the event are put into a queue. 1541 * This prevents the event handling from blocking normal cache operations. 1542 * <p> 1543 * @param element the item 1544 * @param eventType the event type 1545 */ 1546 public void handleElementEvent(final ICacheElement<K, V> element, final ElementEventType eventType) 1547 { 1548 final ArrayList<IElementEventHandler> eventHandlers = element.getElementAttributes().getElementEventHandlers(); 1549 if (eventHandlers != null) 1550 { 1551 log.debug("Element Handlers are registered. Create event type {0}", eventType); 1552 if (elementEventQ == null) 1553 { 1554 log.warn("No element event queue available for cache {0}", this::getCacheName); 1555 return; 1556 } 1557 final IElementEvent<ICacheElement<K, V>> event = new ElementEvent<>(element, eventType); 1558 for (final IElementEventHandler hand : eventHandlers) 1559 { 1560 try 1561 { 1562 elementEventQ.addElementEvent(hand, event); 1563 } 1564 catch (final IOException e) 1565 { 1566 log.error("Trouble adding element event to queue", e); 1567 } 1568 } 1569 } 1570 } 1571 1572 /** 1573 * Create the MemoryCache based on the config parameters. 1574 * TODO: consider making this an auxiliary, despite its close tie to the CacheHub. 1575 * TODO: might want to create a memory cache config file separate from that of the hub -- ICompositeCacheAttributes 1576 * <p> 1577 * @param cattr 1578 */ 1579 private void createMemoryCache(final ICompositeCacheAttributes cattr) 1580 { 1581 if (memCache == null) 1582 { 1583 try 1584 { 1585 final Class<?> c = Class.forName(cattr.getMemoryCacheName()); 1586 @SuppressWarnings("unchecked") // Need cast 1587 final 1588 IMemoryCache<K, V> newInstance = 1589 (IMemoryCache<K, V>) c.getDeclaredConstructor().newInstance(); 1590 memCache = newInstance; 1591 memCache.initialize(this); 1592 } 1593 catch (final Exception e) 1594 { 1595 log.warn("Failed to init mem cache, using: LRUMemoryCache", e); 1596 1597 this.memCache = new LRUMemoryCache<>(); 1598 this.memCache.initialize(this); 1599 } 1600 } 1601 else 1602 { 1603 log.warn("Refusing to create memory cache -- already exists."); 1604 } 1605 } 1606 1607 /** 1608 * Access to the memory cache for instrumentation. 1609 * <p> 1610 * @return the MemoryCache implementation 1611 */ 1612 public IMemoryCache<K, V> getMemoryCache() 1613 { 1614 return memCache; 1615 } 1616 1617 /** 1618 * Number of times a requested item was found in the memory cache. 1619 * <p> 1620 * @return number of hits in memory 1621 */ 1622 public long getHitCountRam() 1623 { 1624 return hitCountRam.get(); 1625 } 1626 1627 /** 1628 * Number of times a requested item was found in and auxiliary cache. 1629 * @return number of auxiliary hits. 1630 */ 1631 public long getHitCountAux() 1632 { 1633 return hitCountAux.get(); 1634 } 1635 1636 /** 1637 * Number of times a requested element was not found. 1638 * @return number of misses. 1639 */ 1640 public long getMissCountNotFound() 1641 { 1642 return missCountNotFound.get(); 1643 } 1644 1645 /** 1646 * Number of times a requested element was found but was expired. 1647 * @return number of found but expired gets. 1648 */ 1649 public long getMissCountExpired() 1650 { 1651 return missCountExpired.get(); 1652 } 1653 1654 /** 1655 * @return Returns the updateCount. 1656 */ 1657 public long getUpdateCount() 1658 { 1659 return updateCount.get(); 1660 } 1661 1662 /** 1663 * Sets the key matcher used by get matching. 1664 * <p> 1665 * @param keyMatcher 1666 */ 1667 @Override 1668 public void setKeyMatcher(final IKeyMatcher<K> keyMatcher) 1669 { 1670 if (keyMatcher != null) 1671 { 1672 this.keyMatcher = keyMatcher; 1673 } 1674 } 1675 1676 /** 1677 * Returns the key matcher used by get matching. 1678 * <p> 1679 * @return keyMatcher 1680 */ 1681 public IKeyMatcher<K> getKeyMatcher() 1682 { 1683 return this.keyMatcher; 1684 } 1685 1686 /** 1687 * This returns the stats. 1688 * <p> 1689 * @return getStats() 1690 */ 1691 @Override 1692 public String toString() 1693 { 1694 return getStats(); 1695 } 1696}