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