001package org.apache.commons.jcs3.engine.control;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.ListIterator;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Objects;
032import java.util.Set;
033import java.util.concurrent.CopyOnWriteArrayList;
034import java.util.concurrent.ScheduledExecutorService;
035import java.util.concurrent.ScheduledFuture;
036import java.util.concurrent.TimeUnit;
037import java.util.concurrent.atomic.AtomicBoolean;
038import java.util.concurrent.atomic.AtomicLong;
039import java.util.stream.Collectors;
040import java.util.stream.Stream;
041
042import org.apache.commons.jcs3.access.exception.CacheException;
043import org.apache.commons.jcs3.access.exception.ObjectNotFoundException;
044import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
045import org.apache.commons.jcs3.engine.CacheStatus;
046import org.apache.commons.jcs3.engine.behavior.ICache;
047import org.apache.commons.jcs3.engine.behavior.ICacheElement;
048import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
049import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
050import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
051import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
052import org.apache.commons.jcs3.engine.control.event.ElementEvent;
053import org.apache.commons.jcs3.engine.control.event.behavior.ElementEventType;
054import org.apache.commons.jcs3.engine.control.event.behavior.IElementEvent;
055import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventHandler;
056import org.apache.commons.jcs3.engine.control.event.behavior.IElementEventQueue;
057import org.apache.commons.jcs3.engine.control.group.GroupId;
058import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
059import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
060import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
061import org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache;
062import org.apache.commons.jcs3.engine.memory.shrinking.ShrinkerThread;
063import org.apache.commons.jcs3.engine.stats.CacheStats;
064import org.apache.commons.jcs3.engine.stats.StatElement;
065import org.apache.commons.jcs3.engine.stats.behavior.ICacheStats;
066import org.apache.commons.jcs3.engine.stats.behavior.IStats;
067import org.apache.commons.jcs3.log.Log;
068import org.apache.commons.jcs3.log.LogManager;
069
070/**
071 * This is the primary hub for a single cache/region. It controls the flow of items through the
072 * cache. The auxiliary and memory caches are plugged in here.
073 * <p>
074 * This is the core of a JCS region. Hence, this simple class is the core of JCS.
075 */
076public class CompositeCache<K, V>
077    implements ICache<K, V>, IRequireScheduler
078{
079    /** log instance */
080    private static final Log log = LogManager.getLog(CompositeCache.class);
081
082    /**
083     * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager
084     * should pass a shared queue in.
085     */
086    private IElementEventQueue elementEventQ;
087
088    /** Auxiliary caches. */
089    private CopyOnWriteArrayList<AuxiliaryCache<K, V>> auxCaches = new CopyOnWriteArrayList<>();
090
091    /** is this alive? */
092    private final AtomicBoolean alive;
093
094    /** Region Elemental Attributes, default. */
095    private IElementAttributes attr;
096
097    /** Cache Attributes, for hub and memory auxiliary. */
098    private ICompositeCacheAttributes cacheAttr;
099
100    /** How many times update was called. */
101    private final AtomicLong updateCount;
102
103    /** How many times remove was called. */
104    private final AtomicLong removeCount;
105
106    /** Memory cache hit count */
107    private final AtomicLong hitCountRam;
108
109    /** Auxiliary cache hit count (number of times found in ANY auxiliary) */
110    private final AtomicLong hitCountAux;
111
112    /** Count of misses where element was not found. */
113    private final AtomicLong missCountNotFound;
114
115    /** Count of misses where element was expired. */
116    private final AtomicLong missCountExpired;
117
118    /** Cache manager. */
119    private CompositeCacheManager cacheManager;
120
121    /**
122     * The cache hub can only have one memory cache. This could be made more flexible in the future,
123     * but they are tied closely together. More than one doesn't make much sense.
124     */
125    private IMemoryCache<K, V> memCache;
126
127    /** Key matcher used by the getMatching API */
128    private IKeyMatcher<K> keyMatcher = new KeyMatcherPatternImpl<>();
129
130    private ScheduledFuture<?> future;
131
132    /**
133     * Constructor for the Cache object
134     * <p>
135     * @param cattr The cache attribute
136     * @param attr The default element attributes
137     */
138    public CompositeCache(final ICompositeCacheAttributes cattr, final IElementAttributes attr)
139    {
140        this.attr = attr;
141        this.cacheAttr = cattr;
142        this.alive = new AtomicBoolean(true);
143        this.updateCount = new AtomicLong();
144        this.removeCount = new AtomicLong();
145        this.hitCountRam = new AtomicLong();
146        this.hitCountAux = new AtomicLong();
147        this.missCountNotFound = new AtomicLong();
148        this.missCountExpired = new AtomicLong();
149
150        createMemoryCache(cattr);
151
152        log.info("Constructed cache with name [{0}] and cache attributes {1}",
153                cacheAttr.getCacheName(), cattr);
154    }
155
156    /**
157     * Injector for Element event queue
158     *
159     * @param queue
160     */
161    public void setElementEventQueue(final IElementEventQueue queue)
162    {
163        this.elementEventQ = queue;
164    }
165
166    /**
167     * Injector for cache manager
168     *
169     * @param manager
170     */
171    public void setCompositeCacheManager(final CompositeCacheManager manager)
172    {
173        this.cacheManager = manager;
174    }
175
176    /**
177     * @see org.apache.commons.jcs3.engine.behavior.IRequireScheduler#setScheduledExecutorService(java.util.concurrent.ScheduledExecutorService)
178     */
179    @Override
180    public void setScheduledExecutorService(final ScheduledExecutorService scheduledExecutor)
181    {
182        if (cacheAttr.isUseMemoryShrinker())
183        {
184            future = scheduledExecutor.scheduleAtFixedRate(
185                    new ShrinkerThread<>(this), 0, cacheAttr.getShrinkerIntervalSeconds(),
186                    TimeUnit.SECONDS);
187        }
188
189        if (memCache instanceof IRequireScheduler)
190        {
191            ((IRequireScheduler) memCache).setScheduledExecutorService(scheduledExecutor);
192        }
193    }
194
195    /**
196     * This sets the list of auxiliary caches for this region.
197     * It filters out null caches
198     * <p>
199     * @param auxCaches
200     * @since 3.1
201     */
202    public void setAuxCaches(final List<AuxiliaryCache<K, V>> auxCaches)
203    {
204        this.auxCaches = auxCaches.stream()
205                .filter(Objects::nonNull)
206                .collect(Collectors.toCollection(CopyOnWriteArrayList::new));
207    }
208
209    /**
210     * This sets the list of auxiliary caches for this region.
211     * <p>
212     * @param auxCaches
213     * @deprecated Use List method
214     */
215    @Deprecated
216    public void setAuxCaches(final AuxiliaryCache<K, V>[] auxCaches)
217    {
218        setAuxCaches(Arrays.asList(auxCaches));
219    }
220
221    /**
222     * Get the list of auxiliary caches for this region.
223     * <p>
224     * @return a list of auxiliary caches, may be empty, never null
225     * @since 3.1
226     */
227    public List<AuxiliaryCache<K, V>> getAuxCacheList()
228    {
229        return this.auxCaches;
230    }
231
232    /**
233     * Get the list of auxiliary caches for this region.
234     * <p>
235     * @return an array of auxiliary caches, may be empty, never null
236     * @deprecated Use List method
237     */
238    @SuppressWarnings("unchecked") // No generic arrays in Java
239    @Deprecated
240    public AuxiliaryCache<K, V>[] getAuxCaches()
241    {
242        return getAuxCacheList().toArray(new AuxiliaryCache[0]);
243    }
244
245    /**
246     * Standard update method.
247     * <p>
248     * @param ce
249     * @throws IOException
250     */
251    @Override
252    public void update(final ICacheElement<K, V> ce)
253        throws IOException
254    {
255        update(ce, false);
256    }
257
258    /**
259     * Standard update method.
260     * <p>
261     * @param ce
262     * @throws IOException
263     */
264    public void localUpdate(final ICacheElement<K, V> ce)
265        throws IOException
266    {
267        update(ce, true);
268    }
269
270    /**
271     * Put an item into the cache. If it is localOnly, then do no notify remote or lateral
272     * auxiliaries.
273     * <p>
274     * @param cacheElement the ICacheElement&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}