1 package org.apache.jcs.engine.memory.mru;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
109 mrulist.remove( key );
110 }
111 mrulist.addFirst( key );
112 }
113
114
115 int size = map.size();
116
117 if ( size < this.cattr.getMaxObjects() )
118 {
119 return;
120 }
121
122
123 if ( log.isDebugEnabled() )
124 {
125 log.debug( "In RAM overflow" );
126 }
127
128
129 try
130 {
131
132
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
143
144
145 for ( int i = 0; i < chunkSizeCorrected; i++ )
146 {
147
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
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
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
186
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
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
353 if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
354 {
355
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
367 mrulist.remove( keyR );
368 removed = true;
369 }
370 }
371 }
372 }
373 else if ( key instanceof GroupId )
374 {
375
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
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
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
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
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
453
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
490 IStatElement[] ses = (IStatElement[]) elems.toArray( new StatElement[0] );
491 stats.setStatElements( ses );
492
493 return stats;
494 }
495 }