View Javadoc
1   package org.apache.commons.jcs.engine.control;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.concurrent.ScheduledExecutorService;
30  import java.util.concurrent.ScheduledFuture;
31  import java.util.concurrent.TimeUnit;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  import org.apache.commons.jcs.access.exception.CacheException;
36  import org.apache.commons.jcs.access.exception.ObjectNotFoundException;
37  import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
38  import org.apache.commons.jcs.engine.CacheConstants;
39  import org.apache.commons.jcs.engine.CacheStatus;
40  import org.apache.commons.jcs.engine.behavior.ICache;
41  import org.apache.commons.jcs.engine.behavior.ICacheElement;
42  import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
43  import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
44  import org.apache.commons.jcs.engine.behavior.IElementAttributes;
45  import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
46  import org.apache.commons.jcs.engine.control.event.ElementEvent;
47  import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
48  import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
49  import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
50  import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
51  import org.apache.commons.jcs.engine.control.group.GroupId;
52  import org.apache.commons.jcs.engine.match.KeyMatcherPatternImpl;
53  import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
54  import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
55  import org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache;
56  import org.apache.commons.jcs.engine.memory.shrinking.ShrinkerThread;
57  import org.apache.commons.jcs.engine.stats.CacheStats;
58  import org.apache.commons.jcs.engine.stats.StatElement;
59  import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
60  import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
61  import org.apache.commons.jcs.engine.stats.behavior.IStats;
62  import org.apache.commons.logging.Log;
63  import org.apache.commons.logging.LogFactory;
64  
65  /**
66   * This is the primary hub for a single cache/region. It controls the flow of items through the
67   * cache. The auxiliary and memory caches are plugged in here.
68   * <p>
69   * This is the core of a JCS region. Hence, this simple class is the core of JCS.
70   */
71  public class CompositeCache<K, V>
72      implements ICache<K, V>, IRequireScheduler
73  {
74      /** log instance */
75      private static final Log log = LogFactory.getLog( CompositeCache.class );
76  
77      /**
78       * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager
79       * should pass a shared queue in.
80       */
81      private IElementEventQueue elementEventQ;
82  
83      /** Auxiliary caches. */
84      @SuppressWarnings("unchecked") // OK because this is an empty array
85      private AuxiliaryCache<K, V>[] auxCaches = new AuxiliaryCache[0];
86  
87      /** is this alive? */
88      private AtomicBoolean alive;
89  
90      /** Region Elemental Attributes, default. */
91      private IElementAttributes attr;
92  
93      /** Cache Attributes, for hub and memory auxiliary. */
94      private ICompositeCacheAttributes cacheAttr;
95  
96      /** How many times update was called. */
97      private AtomicInteger updateCount;
98  
99      /** 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&lt;K, V&gt;
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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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 -&gt; result.
899      * <p>
900      * @param pattern
901      * @return a map of K key to ICacheElement&lt;K, V&gt; 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&lt;K, V&gt; 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 }