View Javadoc

1   package org.apache.jcs.engine.memory;
2   
3   import java.io.IOException;
4   import java.io.Serializable;
5   import java.util.ArrayList;
6   import java.util.Hashtable;
7   import java.util.Iterator;
8   import java.util.LinkedHashSet;
9   import java.util.Map;
10  import java.util.Map.Entry;
11  import java.util.Set;
12  
13  import org.apache.commons.logging.Log;
14  import org.apache.commons.logging.LogFactory;
15  import org.apache.jcs.engine.CacheConstants;
16  import org.apache.jcs.engine.behavior.ICacheElement;
17  import org.apache.jcs.engine.control.CompositeCache;
18  import org.apache.jcs.engine.control.group.GroupAttrName;
19  import org.apache.jcs.engine.memory.util.MemoryElementDescriptor;
20  import org.apache.jcs.engine.stats.StatElement;
21  import org.apache.jcs.engine.stats.Stats;
22  import org.apache.jcs.engine.stats.behavior.IStatElement;
23  import org.apache.jcs.engine.stats.behavior.IStats;
24  import org.apache.jcs.utils.struct.DoubleLinkedList;
25  
26  /**
27   * This class contains methods that are common to memory caches using the double linked list, such
28   * as the LRU, MRU, FIFO, and LIFO caches.
29   * <p>
30   * Children can control the expiration algorithm by controlling the update and get. The last item in
31   * the list will be the one removed when the list fills. For instance LRU should more items to the
32   * front as they are used. FIFO should simply add new items to the front of the list.
33   */
34  public abstract class AbstractDoubleLinkedListMemoryCache<K extends Serializable, V extends Serializable>
35      extends AbstractMemoryCache<K, V>
36  {
37      /** Don't change. */
38      private static final long serialVersionUID = 1422569420563967389L;
39  
40      /** The logger. */
41      private final static Log log = LogFactory.getLog( AbstractDoubleLinkedListMemoryCache.class );
42  
43      /** thread-safe double linked list for lru */
44      protected DoubleLinkedList<MemoryElementDescriptor<K, V>> list;
45  
46      /** number of hits */
47      protected int hitCnt = 0;
48  
49      /** number of misses */
50      protected int missCnt = 0;
51  
52      /** number of puts */
53      private int putCnt = 0;
54  
55      /**
56       * For post reflection creation initialization.
57       * <p>
58       * @param hub
59       */
60      @Override
61      public synchronized void initialize( CompositeCache<K, V> hub )
62      {
63          super.initialize( hub );
64          list = new DoubleLinkedList<MemoryElementDescriptor<K, V>>();
65          log.info( "initialized MemoryCache for " + cacheName );
66      }
67  
68      /**
69       * This is called by super initialize.
70       * <p>
71       * @return new Hashtable()
72       */
73      @Override
74      public Map<K, MemoryElementDescriptor<K, V>> createMap()
75      {
76          return new Hashtable<K, MemoryElementDescriptor<K, V>>();
77      }
78  
79      /**
80       * Calls the abstract method updateList.
81       * <p>
82       * If the max size is reached, an element will be put to disk.
83       * <p>
84       * @param ce The cache element, or entry wrapper
85       * @exception IOException
86       */
87      @Override
88      public final void update( ICacheElement<K, V> ce )
89          throws IOException
90      {
91          putCnt++;
92          ce.getElementAttributes().setLastAccessTimeNow();
93  
94          synchronized ( this )
95          {
96              // ABSTRACT
97              MemoryElementDescriptor<K, V> newNode = adjustListForUpdate( ce );
98  
99              // this must be synchronized
100             MemoryElementDescriptor<K, V> oldNode = map.put( newNode.ce.getKey(), newNode );
101 
102             // If the node was the same as an existing node, remove it.
103             if ( oldNode != null && ( newNode.ce.getKey().equals( oldNode.ce.getKey() ) ) )
104             {
105                 list.remove( oldNode );
106             }
107         }
108 
109         // If we are over the max spool some
110         spoolIfNeeded();
111     }
112 
113     /**
114      * Children implement this to control the cache expiration algorithm
115      * <p>
116      * @param ce
117      * @return MemoryElementDescriptor the new node
118      * @throws IOException
119      */
120     protected abstract MemoryElementDescriptor<K, V> adjustListForUpdate( ICacheElement<K, V> ce )
121         throws IOException;
122 
123     /**
124      * If the max size has been reached, spool.
125      * <p>
126      * @throws Error
127      */
128     private void spoolIfNeeded()
129         throws Error
130     {
131         int size = map.size();
132         // If the element limit is reached, we need to spool
133 
134         if ( size <= this.cacheAttributes.getMaxObjects() )
135         {
136             return;
137         }
138 
139         if ( log.isDebugEnabled() )
140         {
141             log.debug( "In memory limit reached, spooling" );
142         }
143 
144         // Write the last 'chunkSize' items to disk.
145         int chunkSizeCorrected = Math.min( size, chunkSize );
146 
147         if ( log.isDebugEnabled() )
148         {
149             log.debug( "About to spool to disk cache, map size: " + size + ", max objects: "
150                 + this.cacheAttributes.getMaxObjects() + ", items to spool: " + chunkSizeCorrected );
151         }
152 
153         // The spool will put them in a disk event queue, so there is no
154         // need to pre-queue the queuing. This would be a bit wasteful
155         // and wouldn't save much time in this synchronous call.
156         for ( int i = 0; i < chunkSizeCorrected; i++ )
157         {
158             spoolLastElement();
159         }
160 
161         if ( log.isDebugEnabled() )
162         {
163             log.debug( "update: After spool map size: " + map.size() + " linked list size = " + dumpCacheSize() );
164         }
165     }
166 
167     /**
168      * Get an item from the cache If the item is found, it is removed from the list and added first.
169      * <p>
170      * @param key Identifies item to find
171      * @return ICacheElement<K, V> if found, else null
172      * @exception IOException
173      */
174     @Override
175     public final synchronized ICacheElement<K, V> get( K key )
176         throws IOException
177     {
178         ICacheElement<K, V> ce = null;
179 
180         if ( log.isDebugEnabled() )
181         {
182             log.debug( "getting item from cache " + cacheName + " for key " + key );
183         }
184 
185         MemoryElementDescriptor<K, V> me = map.get( key );
186 
187         if ( me != null )
188         {
189             ce = me.ce;
190             hitCnt++;
191             ce.getElementAttributes().setLastAccessTimeNow();
192             if ( log.isDebugEnabled() )
193             {
194                 log.debug( cacheName + ": LRUMemoryCache hit for " + ce.getKey() );
195             }
196 
197             // ABSTRACT
198             adjustListForGet( me );
199         }
200         else
201         {
202             missCnt++;
203             if ( log.isDebugEnabled() )
204             {
205                 log.debug( cacheName + ": LRUMemoryCache miss for " + key );
206             }
207         }
208 
209         verifyCache();
210         return ce;
211     }
212 
213     /**
214      * Adjust the list as needed for a get. This allows children to control the algorithm
215      * <p>
216      * @param me
217      */
218     protected abstract void adjustListForGet( MemoryElementDescriptor<K, V> me );
219 
220     /**
221      * This instructs the memory cache to remove the <i>numberToFree</i> according to its eviction
222      * policy. For example, the LRUMemoryCache will remove the <i>numberToFree</i> least recently
223      * used items. These will be spooled to disk if a disk auxiliary is available.
224      * <p>
225      * @param numberToFree
226      * @return the number that were removed. if you ask to free 5, but there are only 3, you will
227      *         get 3.
228      * @throws IOException
229      */
230     public int freeElements( int numberToFree )
231         throws IOException
232     {
233         int freed = 0;
234         for ( ; freed < numberToFree; freed++ )
235         {
236             ICacheElement<K, V> element = spoolLastElement();
237             if ( element == null )
238             {
239                 break;
240             }
241         }
242         return freed;
243     }
244 
245     /**
246      * This spools the last element in the LRU, if one exists.
247      * <p>
248      * @return ICacheElement<K, V> if there was a last element, else null.
249      * @throws Error
250      */
251     protected ICacheElement<K, V> spoolLastElement()
252         throws Error
253     {
254         ICacheElement<K, V> toSpool = null;
255         synchronized ( this )
256         {
257             if ( list.getLast() != null )
258             {
259                 toSpool = list.getLast().ce;
260                 if ( toSpool != null )
261                 {
262                     cache.spoolToDisk( list.getLast().ce );
263                     if ( !map.containsKey( list.getLast().ce.getKey() ) )
264                     {
265                         log.error( "update: map does not contain key: "
266                             + list.getLast().ce.getKey() );
267                         verifyCache();
268                     }
269                     if ( map.remove( list.getLast().ce.getKey() ) == null )
270                     {
271                         log.warn( "update: remove failed for key: "
272                             + list.getLast().ce.getKey() );
273                         verifyCache();
274                     }
275                 }
276                 else
277                 {
278                     throw new Error( "update: last.ce is null!" );
279                 }
280                 list.removeLast();
281             }
282             else
283             {
284                 verifyCache();
285                 throw new Error( "update: last is null!" );
286             }
287 
288             // If this is out of the sync block it can detect a mismatch
289             // where there is none.
290             if ( map.size() != dumpCacheSize() )
291             {
292                 log.warn( "update: After spool, size mismatch: map.size() = " + map.size() + ", linked list size = "
293                     + dumpCacheSize() );
294             }
295         }
296         return toSpool;
297     }
298 
299     /**
300      * Removes an item from the cache. This method handles hierarchical removal. If the key is a
301      * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
302      * starting with the argument String will be removed.
303      * <p>
304      * @param key
305      * @return true if the removal was successful
306      * @exception IOException
307      */
308     @Override
309     public synchronized boolean remove( K key )
310         throws IOException
311     {
312         if ( log.isDebugEnabled() )
313         {
314             log.debug( "removing item for key: " + key );
315         }
316 
317         boolean removed = false;
318 
319         // handle partial removal
320         if ( key instanceof String && ( (String) key ).endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
321         {
322             // remove all keys of the same name hierarchy.
323             synchronized ( map )
324             {
325                 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); )
326                 {
327                     Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
328                     K k = entry.getKey();
329 
330                     if ( k instanceof String && ( (String) k ).startsWith( key.toString() ) )
331                     {
332                         list.remove( entry.getValue() );
333                         itr.remove();
334                         removed = true;
335                     }
336                 }
337             }
338         }
339         else if ( key instanceof GroupAttrName )
340         {
341             // remove all keys of the same name hierarchy.
342             synchronized ( map )
343             {
344                 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); )
345                 {
346                     Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
347                     K k = entry.getKey();
348 
349                     if ( k instanceof GroupAttrName &&
350                         ((GroupAttrName<?>)k).groupId.equals(((GroupAttrName<?>)key).groupId))
351                     {
352                         list.remove( entry.getValue() );
353                         itr.remove();
354                         removed = true;
355                     }
356                 }
357             }
358         }
359         else
360         {
361             // remove single item.
362             MemoryElementDescriptor<K, V> me = map.remove( key );
363 
364             if ( me != null )
365             {
366                 list.remove( me );
367                 removed = true;
368             }
369         }
370 
371         return removed;
372     }
373 
374     /**
375      * Remove all of the elements from both the Map and the linked list implementation. Overrides
376      * base class.
377      * <p>
378      * @throws IOException
379      */
380     @Override
381     public synchronized void removeAll()
382         throws IOException
383     {
384         map.clear();
385         list.removeAll();
386     }
387 
388     // --------------------------- internal methods (linked list implementation)
389     /**
390      * Adds a new node to the start of the link list.
391      * <p>
392      * @param ce The feature to be added to the First
393      * @return MemoryElementDescriptor
394      */
395     protected synchronized MemoryElementDescriptor<K, V> addFirst( ICacheElement<K, V> ce )
396     {
397         MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<K, V>( ce );
398         list.addFirst( me );
399         verifyCache( ce.getKey() );
400         return me;
401     }
402 
403     /**
404      * Adds a new node to the end of the link list.
405      * <p>
406      * @param ce The feature to be added to the First
407      * @return MemoryElementDescriptor
408      */
409     protected synchronized MemoryElementDescriptor<K, V> addLast( ICacheElement<K, V> ce )
410     {
411         MemoryElementDescriptor<K, V> me = new MemoryElementDescriptor<K, V>( ce );
412         list.addLast( me );
413         verifyCache( ce.getKey() );
414         return me;
415     }
416 
417     // ---------------------------------------------------------- debug methods
418 
419     /**
420      * Dump the cache entries from first to list for debugging.
421      */
422     @SuppressWarnings("unchecked") // No generics for public fields
423     public void dumpCacheEntries()
424     {
425         log.debug( "dumpingCacheEntries" );
426         for ( MemoryElementDescriptor<K, V> me = list.getFirst(); me != null; me = (MemoryElementDescriptor<K, V>) me.next )
427         {
428             log.debug( "dumpCacheEntries> key=" + me.ce.getKey() + ", val=" + me.ce.getVal() );
429         }
430     }
431 
432     /**
433      * Returns the size of the list.
434      * <p>
435      * @return the number of items in the map.
436      */
437     protected int dumpCacheSize()
438     {
439         return list.size();
440     }
441 
442     /**
443      * Checks to see if all the items that should be in the cache are. Checks consistency between
444      * List and map.
445      */
446     @SuppressWarnings("unchecked") // No generics for public fields
447     protected void verifyCache()
448     {
449         if ( !log.isDebugEnabled() )
450         {
451             return;
452         }
453 
454         boolean found = false;
455         log.debug( "verifycache[" + cacheName + "]: mapContains " + map.size() + " elements, linked list contains "
456             + dumpCacheSize() + " elements" );
457         log.debug( "verifycache: checking linked list by key " );
458         for ( MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next )
459         {
460             Object key = li.ce.getKey();
461             if ( !map.containsKey( key ) )
462             {
463                 log.error( "verifycache[" + cacheName + "]: map does not contain key : " + li.ce.getKey() );
464                 log.error( "li.hashcode=" + li.ce.getKey().hashCode() );
465                 log.error( "key class=" + key.getClass() );
466                 log.error( "key hashcode=" + key.hashCode() );
467                 log.error( "key toString=" + key.toString() );
468                 if ( key instanceof GroupAttrName )
469                 {
470                     GroupAttrName<?> name = (GroupAttrName<?>) key;
471                     log.error( "GroupID hashcode=" + name.groupId.hashCode() );
472                     log.error( "GroupID.class=" + name.groupId.getClass() );
473                     log.error( "AttrName hashcode=" + name.attrName.hashCode() );
474                     log.error( "AttrName.class=" + name.attrName.getClass() );
475                 }
476                 dumpMap();
477             }
478             else if ( map.get( li.ce.getKey() ) == null )
479             {
480                 log.error( "verifycache[" + cacheName + "]: linked list retrieval returned null for key: "
481                     + li.ce.getKey() );
482             }
483         }
484 
485         log.debug( "verifycache: checking linked list by value " );
486         for ( MemoryElementDescriptor<K, V> li3 = list.getFirst(); li3 != null; li3 = (MemoryElementDescriptor<K, V>) li3.next )
487         {
488             if ( map.containsValue( li3 ) == false )
489             {
490                 log.error( "verifycache[" + cacheName + "]: map does not contain value : " + li3 );
491                 dumpMap();
492             }
493         }
494 
495         log.debug( "verifycache: checking via keysets!" );
496         for (Serializable val : map.keySet())
497         {
498             found = false;
499 
500             for ( MemoryElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (MemoryElementDescriptor<K, V>) li2.next )
501             {
502                 if ( val.equals( li2.ce.getKey() ) )
503                 {
504                     found = true;
505                     break;
506                 }
507             }
508             if ( !found )
509             {
510                 log.error( "verifycache[" + cacheName + "]: key not found in list : " + val );
511                 dumpCacheEntries();
512                 if ( map.containsKey( val ) )
513                 {
514                     log.error( "verifycache: map contains key" );
515                 }
516                 else
517                 {
518                     log.error( "verifycache: map does NOT contain key, what the HECK!" );
519                 }
520             }
521         }
522     }
523 
524     /**
525      * Logs an error if an element that should be in the cache is not.
526      * <p>
527      * @param key
528      */
529     @SuppressWarnings("unchecked") // No generics for public fields
530     private void verifyCache( K key )
531     {
532         if ( !log.isDebugEnabled() )
533         {
534             return;
535         }
536 
537         boolean found = false;
538 
539         // go through the linked list looking for the key
540         for ( MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; li = (MemoryElementDescriptor<K, V>) li.next )
541         {
542             if ( li.ce.getKey() == key )
543             {
544                 found = true;
545                 log.debug( "verifycache(key) key match: " + key );
546                 break;
547             }
548         }
549         if ( !found )
550         {
551             log.error( "verifycache(key)[" + cacheName + "], couldn't find key! : " + key );
552         }
553     }
554 
555     // --------------------------- iteration methods (iteration helpers)
556     /**
557      * iteration aid
558      */
559     public static class IteratorWrapper<K extends Serializable, V extends Serializable>
560         implements Iterator<Entry<K, MemoryElementDescriptor<K, V>>>
561     {
562         /** The internal iterator */
563         private final Iterator<Entry<K, MemoryElementDescriptor<K, V>>> i;
564 
565         /**
566          * Wrapped to remove our wrapper object
567          * @param m
568          */
569         protected IteratorWrapper(Map<K, MemoryElementDescriptor<K, V>> m)
570         {
571             i = m.entrySet().iterator();
572         }
573 
574         /** @return i.hasNext() */
575         public boolean hasNext()
576         {
577             return i.hasNext();
578         }
579 
580         /** @return new MapEntryWrapper( (Map.Entry) i.next() ) */
581         public Entry<K, MemoryElementDescriptor<K, V>> next()
582         {
583             // return new MapEntryWrapper<Serializable>( i.next() );
584             return i.next();
585         }
586 
587         /** i.remove(); */
588         public void remove()
589         {
590             i.remove();
591         }
592 
593         /**
594          * @param o
595          * @return i.equals( o ))
596          */
597         @Override
598         public boolean equals( Object o )
599         {
600             return i.equals( o );
601         }
602 
603         /** @return i.hashCode() */
604         @Override
605         public int hashCode()
606         {
607             return i.hashCode();
608         }
609     }
610 
611     /**
612      * @author Aaron Smuts
613      */
614     public static class MapEntryWrapper<K extends Serializable, V extends Serializable>
615         implements Map.Entry<K, ICacheElement<K, V>>
616     {
617         /** The internal entry */
618         private final Map.Entry<K, MemoryElementDescriptor<K, V>> e;
619 
620         /**
621          * @param e
622          */
623         private MapEntryWrapper( Map.Entry<K, MemoryElementDescriptor<K, V>> e )
624         {
625             this.e = e;
626         }
627 
628         /**
629          * @param o
630          * @return e.equals( o )
631          */
632         @Override
633         public boolean equals( Object o )
634         {
635             return e.equals( o );
636         }
637 
638         /** @return e.getKey() */
639         public K getKey()
640         {
641             return e.getKey();
642         }
643 
644         /** @return ( (MemoryElementDescriptor) e.getValue() ).ce */
645         public ICacheElement<K, V> getValue()
646         {
647             return e.getValue().ce;
648         }
649 
650         /** @return e.hashCode() */
651         @Override
652         public int hashCode()
653         {
654             return e.hashCode();
655         }
656 
657         /**
658          * invalid
659          * @param value
660          * @return always throws
661          */
662         public ICacheElement<K, V> setValue(ICacheElement<K, V> value)
663         {
664             throw new UnsupportedOperationException( "Use normal cache methods"
665                 + " to alter the contents of the cache." );
666         }
667     }
668 
669     /**
670      * Gets the iterator attribute of the LRUMemoryCache object
671      * <p>
672      * @return The iterator value
673      */
674     @Override
675     public Iterator<Entry<K, MemoryElementDescriptor<K, V>>> getIterator()
676     {
677         return new IteratorWrapper<K, V>( map );
678     }
679 
680     /**
681      * Get an Array of the keys for all elements in the memory cache
682      * @return An Object[]
683      */
684     @Override
685     public Set<K> getKeySet()
686     {
687         // need a better locking strategy here.
688         synchronized ( this )
689         {
690             // may need to lock to map here?
691             return new LinkedHashSet<K>(map.keySet());
692         }
693     }
694 
695     /**
696      * This returns semi-structured information on the memory cache, such as the size, put count,
697      * hit count, and miss count.
698      * <p>
699      * @see org.apache.jcs.engine.memory.IMemoryCache#getStatistics()
700      */
701     @Override
702     public synchronized IStats getStatistics()
703     {
704         IStats stats = new Stats();
705         stats.setTypeName( /*add algorithm name*/"Memory Cache" );
706 
707         ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
708 
709         IStatElement se = null;
710 
711         se = new StatElement();
712         se.setName( "List Size" );
713         se.setData( "" + list.size() );
714         elems.add( se );
715 
716         se = new StatElement();
717         se.setName( "Map Size" );
718         se.setData( "" + map.size() );
719         elems.add( se );
720 
721         se = new StatElement();
722         se.setName( "Put Count" );
723         se.setData( "" + putCnt );
724         elems.add( se );
725 
726         se = new StatElement();
727         se.setName( "Hit Count" );
728         se.setData( "" + hitCnt );
729         elems.add( se );
730 
731         se = new StatElement();
732         se.setName( "Miss Count" );
733         se.setData( "" + missCnt );
734         elems.add( se );
735 
736         // get an array and put them in the Stats object
737         IStatElement[] ses = elems.toArray( new StatElement[0] );
738         stats.setStatElements( ses );
739 
740         return stats;
741     }
742 }