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