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