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