001package org.apache.commons.jcs.engine.control;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.ScheduledExecutorService;
030import java.util.concurrent.ScheduledFuture;
031import java.util.concurrent.TimeUnit;
032import java.util.concurrent.atomic.AtomicBoolean;
033import java.util.concurrent.atomic.AtomicInteger;
034
035import org.apache.commons.jcs.access.exception.CacheException;
036import org.apache.commons.jcs.access.exception.ObjectNotFoundException;
037import org.apache.commons.jcs.auxiliary.AuxiliaryCache;
038import org.apache.commons.jcs.engine.CacheConstants;
039import org.apache.commons.jcs.engine.CacheStatus;
040import org.apache.commons.jcs.engine.behavior.ICache;
041import org.apache.commons.jcs.engine.behavior.ICacheElement;
042import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
043import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes.DiskUsagePattern;
044import org.apache.commons.jcs.engine.behavior.IElementAttributes;
045import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
046import org.apache.commons.jcs.engine.control.event.ElementEvent;
047import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType;
048import org.apache.commons.jcs.engine.control.event.behavior.IElementEvent;
049import org.apache.commons.jcs.engine.control.event.behavior.IElementEventHandler;
050import org.apache.commons.jcs.engine.control.event.behavior.IElementEventQueue;
051import org.apache.commons.jcs.engine.control.group.GroupId;
052import org.apache.commons.jcs.engine.match.KeyMatcherPatternImpl;
053import org.apache.commons.jcs.engine.match.behavior.IKeyMatcher;
054import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
055import org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache;
056import org.apache.commons.jcs.engine.memory.shrinking.ShrinkerThread;
057import org.apache.commons.jcs.engine.stats.CacheStats;
058import org.apache.commons.jcs.engine.stats.StatElement;
059import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
060import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
061import org.apache.commons.jcs.engine.stats.behavior.IStats;
062import org.apache.commons.logging.Log;
063import org.apache.commons.logging.LogFactory;
064
065/**
066 * This is the primary hub for a single cache/region. It controls the flow of items through the
067 * cache. The auxiliary and memory caches are plugged in here.
068 * <p>
069 * This is the core of a JCS region. Hence, this simple class is the core of JCS.
070 */
071public class CompositeCache<K, V>
072    implements ICache<K, V>, IRequireScheduler
073{
074    /** log instance */
075    private static final Log log = LogFactory.getLog( CompositeCache.class );
076
077    /**
078     * EventQueue for handling element events. Lazy initialized. One for each region. To be more efficient, the manager
079     * should pass a shared queue in.
080     */
081    private IElementEventQueue elementEventQ;
082
083    /** Auxiliary caches. */
084    @SuppressWarnings("unchecked") // OK because this is an empty array
085    private AuxiliaryCache<K, V>[] auxCaches = new AuxiliaryCache[0];
086
087    /** is this alive? */
088    private AtomicBoolean alive;
089
090    /** Region Elemental Attributes, default. */
091    private IElementAttributes attr;
092
093    /** Cache Attributes, for hub and memory auxiliary. */
094    private ICompositeCacheAttributes cacheAttr;
095
096    /** How many times update was called. */
097    private AtomicInteger updateCount;
098
099    /** 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&lt;K, V&gt;
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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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&lt;K, V&gt; 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 -&gt; result.
899     * <p>
900     * @param pattern
901     * @return a map of K key to ICacheElement&lt;K, V&gt; 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&lt;K, V&gt; 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}