View Javadoc

1   package org.apache.jcs.engine.memory.mru;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.ListIterator;
28  import java.util.Map;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.jcs.engine.CacheConstants;
33  import org.apache.jcs.engine.behavior.ICacheElement;
34  import org.apache.jcs.engine.control.CompositeCache;
35  import org.apache.jcs.engine.control.group.GroupAttrName;
36  import org.apache.jcs.engine.control.group.GroupId;
37  import org.apache.jcs.engine.memory.AbstractMemoryCache;
38  import org.apache.jcs.engine.stats.StatElement;
39  import org.apache.jcs.engine.stats.Stats;
40  import org.apache.jcs.engine.stats.behavior.IStatElement;
41  import org.apache.jcs.engine.stats.behavior.IStats;
42  
43  /***
44   * A SLOW reference management system. The most recently used items move to the
45   * front of the list and get spooled to disk if the cache hub is configured to
46   * use a disk cache.
47   * <p>
48   * This class is mainly for testing the hub. It also shows that use of the
49   * Collection LinkedList is far slower than JCS' own double linked list.
50   */
51  public class MRUMemoryCache
52      extends AbstractMemoryCache
53  {
54      private static final long serialVersionUID = 5013101678192336129L;
55  
56      private final static Log log = LogFactory.getLog( MRUMemoryCache.class );
57  
58      private int hitCnt = 0;
59  
60      private int missCnt = 0;
61  
62      private int putCnt = 0;
63  
64      /***
65       * Object to lock on the Field
66       */
67      private int[] lockMe = new int[0];
68  
69      /***
70       * MRU list.
71       */
72      private LinkedList mrulist = new LinkedList();
73  
74      /***
75       * For post reflection creation initialization
76       * @param hub
77       */
78      public synchronized void initialize( CompositeCache hub )
79      {
80          super.initialize( hub );
81          log.info( "Initialized MRUMemoryCache for " + cacheName );
82      }
83  
84      /***
85       * Puts an item to the cache.
86       * @param ce
87       * @exception IOException
88       */
89      public void update( ICacheElement ce )
90          throws IOException
91      {
92          putCnt++;
93  
94          Serializable key = ce.getKey();
95          ce.getElementAttributes().setLastAccessTimeNow();
96  
97          // need a more fine grained locking here
98          boolean replace = false;
99          if ( map.containsKey( key ) )
100         {
101             replace = true;
102         }
103         synchronized ( lockMe )
104         {
105             map.put( key, ce );
106             if ( replace )
107             {
108                 // the slowest method I've ever seen
109                 mrulist.remove( key );
110             }
111             mrulist.addFirst( key );
112         }
113 
114         // save a microsecond on the second call.
115         int size = map.size();
116         // need to spool at a certain percentage synchronously
117         if ( size < this.cattr.getMaxObjects() )
118         {
119             return;
120         }
121         // SPOOL LAST -- need to make this a grouping in a queue
122 
123         if ( log.isDebugEnabled() )
124         {
125             log.debug( "In RAM overflow" );
126         }
127 
128         // write the last item to disk.
129         try
130         {
131             // PUSH more than 1 TO DISK TO MINIMIZE THE TYPICAL spool at each
132             // put.
133             int chunkSizeCorrected = Math.min( size, chunkSize );
134 
135             if ( log.isDebugEnabled() )
136             {
137                 log.debug( "update: About to spool to disk cache, map.size() = " + size
138                     + ", this.cattr.getMaxObjects() = " + this.cattr.getMaxObjects() + ", chunkSizeCorrected = "
139                     + chunkSizeCorrected );
140             }
141 
142             // The spool will put them in a disk event queue, so there is no
143             // need to pre-queue the queuing. This would be a bit wasteful
144             // and wouldn't save much time in this synchronous call.
145             for ( int i = 0; i < chunkSizeCorrected; i++ )
146             {
147                 // remove the last
148                 spoolLastElement();
149             }
150 
151             if ( log.isDebugEnabled() )
152             {
153                 log.debug( "update: After spool,  map.size() = " + size + ", this.cattr.getMaxObjects() = "
154                     + this.cattr.getMaxObjects() + ", chunkSizeCorrected = " + chunkSizeCorrected );
155             }
156 
157         }
158         catch ( Exception ex )
159         {
160             // impossible case.
161             log.error( "Problem updating MRU.", ex );
162             throw new IllegalStateException( ex.getMessage() );
163         }
164     }
165 
166     /***
167      * This removes the last elemement in the list.
168      * <p>
169      * @return ICacheElement if there was a last element, else null.
170      */
171     protected ICacheElement spoolLastElement()
172     {
173         ICacheElement toSpool = null;
174 
175         // need a more fine grained locking here
176         synchronized ( lockMe )
177         {
178             Serializable last = (Serializable) mrulist.removeLast();
179             if ( last != null )
180             {
181                 toSpool = (ICacheElement) map.get( last );
182                 map.remove( last );
183             }
184         }
185         // Might want to rename this "overflow" incase the hub
186         // wants to do something else.
187         if ( toSpool != null )
188         {
189             cache.spoolToDisk( toSpool );
190         }
191 
192         return toSpool;
193     }
194 
195     /***
196      * This instructs the memory cache to remove the <i>numberToFree</i>
197      * according to its eviction policy. For example, the LRUMemoryCache will
198      * remove the <i>numberToFree</i> least recently used items. These will be
199      * spooled to disk if a disk auxiliary is available.
200      * <p>
201      * @param numberToFree
202      * @return the number that were removed. if you ask to free 5, but there are
203      *         only 3, you will get 3.
204      * @throws IOException
205      */
206     public int freeElements( int numberToFree )
207         throws IOException
208     {
209         int freed = 0;
210         for ( ; freed < numberToFree; freed++ )
211         {
212             ICacheElement element = spoolLastElement();
213             if ( element == null )
214             {
215                 break;
216             }
217         }
218         return freed;
219     }
220 
221     /***
222      * Get an item from the cache without affecting its last access time or
223      * position.
224      * @return Element matching key if found, or null
225      * @param key
226      *            Identifies item to find
227      * @exception IOException
228      */
229     public ICacheElement getQuiet( Serializable key )
230         throws IOException
231     {
232         ICacheElement ce = null;
233 
234         try
235         {
236             ce = (ICacheElement) map.get( key );
237             if ( ce != null )
238             {
239                 if ( log.isDebugEnabled() )
240                 {
241                     log.debug( cacheName + ": MRUMemoryCache quiet hit for " + key );
242                 }
243             }
244             else
245             {
246                 log.debug( cacheName + ": MRUMemoryCache quiet miss for " + key );
247             }
248         }
249         catch ( Exception e )
250         {
251             log.error( "Problem getting quietly from MRU.", e );
252         }
253 
254         return ce;
255     }
256 
257     /***
258      * Gets an item out of the map. If it finds an item, it is removed from the
259      * list and then added to the first position in the linked list.
260      * @return
261      * @param key
262      * @exception IOException
263      */
264     public ICacheElement get( Serializable key )
265         throws IOException
266     {
267         ICacheElement ce = null;
268         boolean found = false;
269 
270         try
271         {
272             if ( log.isDebugEnabled() )
273             {
274                 log.debug( "get> key=" + key );
275                 log.debug( "get> key=" + key.toString() );
276             }
277 
278             ce = (ICacheElement) map.get( key );
279             if ( log.isDebugEnabled() )
280             {
281                 log.debug( "ce =" + ce );
282             }
283 
284             if ( ce != null )
285             {
286                 found = true;
287                 ce.getElementAttributes().setLastAccessTimeNow();
288                 hitCnt++;
289                 if ( log.isDebugEnabled() )
290                 {
291                     log.debug( cacheName + " -- RAM-HIT for " + key );
292                 }
293             }
294         }
295         catch ( Exception e )
296         {
297             log.error( "Problem getting element.", e );
298         }
299 
300         try
301         {
302             if ( !found )
303             {
304                 // Item not found in cache.
305                 missCnt++;
306                 if ( log.isDebugEnabled() )
307                 {
308                     log.debug( cacheName + " -- MISS for " + key );
309                 }
310                 return null;
311             }
312         }
313         catch ( Exception e )
314         {
315             log.error( "Error handling miss", e );
316             return null;
317         }
318 
319         try
320         {
321             synchronized ( lockMe )
322             {
323                 mrulist.remove( ce.getKey() );
324                 mrulist.addFirst( ce.getKey() );
325             }
326         }
327         catch ( Exception e )
328         {
329             log.error( "Error making first", e );
330             return null;
331         }
332 
333         return ce;
334     }
335 
336     /***
337      * Removes an item from the cache.
338      * @return
339      * @param key
340      * @exception IOException
341      */
342     public boolean remove( Serializable key )
343         throws IOException
344     {
345         if ( log.isDebugEnabled() )
346         {
347             log.debug( "remove> key=" + key );
348         }
349 
350         boolean removed = false;
351 
352         // handle partial removal
353         if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
354         {
355             // remove all keys of the same name hierarchy.
356             synchronized ( lockMe )
357             {
358                 for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
359                 {
360                     Map.Entry entry = (Map.Entry) itr.next();
361                     Object k = entry.getKey();
362                     if ( k instanceof String && k.toString().startsWith( key.toString() ) )
363                     {
364                         itr.remove();
365                         Serializable keyR = (Serializable) entry.getKey();
366                         // map.remove( keyR );
367                         mrulist.remove( keyR );
368                         removed = true;
369                     }
370                 }
371             }
372         }
373         else if ( key instanceof GroupId )
374         {
375             // remove all keys of the same name hierarchy.
376             synchronized ( lockMe )
377             {
378                 for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
379                 {
380                     Map.Entry entry = (Map.Entry) itr.next();
381                     Object k = entry.getKey();
382 
383                     if ( k instanceof GroupAttrName && ( (GroupAttrName) k ).groupId.equals( key ) )
384                     {
385                         itr.remove();
386                         mrulist.remove( k );
387                         removed = true;
388                     }
389                 }
390             }
391         }
392         else
393         {
394             // remove single item.
395             if ( map.containsKey( key ) )
396             {
397                 synchronized ( lockMe )
398                 {
399                     map.remove( key );
400                     mrulist.remove( key );
401                 }
402                 removed = true;
403             }
404         }
405         // end else not hierarchical removal
406         return removed;
407     }
408 
409     /***
410      * Get an Array of the keys for all elements in the memory cache
411      * @return Object[]
412      */
413     public Object[] getKeyArray()
414     {
415         // need to lock to map here?
416         synchronized ( lockMe )
417         {
418             return map.keySet().toArray();
419         }
420     }
421 
422     /***
423      * Dump the cache map for debugging.
424      */
425     public void dumpMap()
426     {
427         log.debug( "dumpingMap" );
428         for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
429         {
430             // for ( Iterator itr = memCache.getIterator(); itr.hasNext();) {
431             Map.Entry e = (Map.Entry) itr.next();
432             ICacheElement ce = (ICacheElement) e.getValue();
433             log.debug( "dumpMap> key=" + e.getKey() + ", val=" + ce.getVal() );
434         }
435     }
436 
437     /***
438      * Dump the cache entries from first to list for debugging.
439      */
440     public void dumpCacheEntries()
441     {
442         log.debug( "dumpingCacheEntries" );
443         ListIterator li = mrulist.listIterator();
444         while ( li.hasNext() )
445         {
446             Serializable key = (Serializable) li.next();
447             log.debug( "dumpCacheEntries> key=" + key + ", val=" + ( (ICacheElement) map.get( key ) ).getVal() );
448         }
449     }
450 
451     /*
452      * (non-Javadoc)
453      * @see org.apache.jcs.engine.memory.MemoryCache#getStatistics()
454      */
455     public IStats getStatistics()
456     {
457         IStats stats = new Stats();
458         stats.setTypeName( "MRU Memory Cache" );
459 
460         ArrayList elems = new ArrayList();
461 
462         IStatElement se = null;
463 
464         se = new StatElement();
465         se.setName( "List Size" );
466         se.setData( "" + mrulist.size() );
467         elems.add( se );
468 
469         se = new StatElement();
470         se.setName( "Map Size" );
471         se.setData( "" + map.size() );
472         elems.add( se );
473 
474         se = new StatElement();
475         se.setName( "Put Count" );
476         se.setData( "" + putCnt );
477         elems.add( se );
478 
479         se = new StatElement();
480         se.setName( "Hit Count" );
481         se.setData( "" + hitCnt );
482         elems.add( se );
483 
484         se = new StatElement();
485         se.setName( "Miss Count" );
486         se.setData( "" + missCnt );
487         elems.add( se );
488 
489         // get an array and put them in the Stats object
490         IStatElement[] ses = (IStatElement[]) elems.toArray( new StatElement[0] );
491         stats.setStatElements( ses );
492 
493         return stats;
494     }
495 }