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