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