1 package org.apache.commons.jcs.auxiliary.disk.block;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.concurrent.ScheduledExecutorService;
34 import java.util.concurrent.ScheduledFuture;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.locks.ReentrantReadWriteLock;
37
38 import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes;
39 import org.apache.commons.jcs.auxiliary.disk.AbstractDiskCache;
40 import org.apache.commons.jcs.engine.CacheConstants;
41 import org.apache.commons.jcs.engine.behavior.ICacheElement;
42 import org.apache.commons.jcs.engine.behavior.IElementSerializer;
43 import org.apache.commons.jcs.engine.behavior.IRequireScheduler;
44 import org.apache.commons.jcs.engine.control.group.GroupAttrName;
45 import org.apache.commons.jcs.engine.control.group.GroupId;
46 import org.apache.commons.jcs.engine.stats.StatElement;
47 import org.apache.commons.jcs.engine.stats.Stats;
48 import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
49 import org.apache.commons.jcs.engine.stats.behavior.IStats;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53
54
55
56
57
58 public class BlockDiskCache<K, V>
59 extends AbstractDiskCache<K, V>
60 implements IRequireScheduler
61 {
62
63 private static final Log log = LogFactory.getLog( BlockDiskCache.class );
64
65
66 private final String logCacheName;
67
68
69 private final String fileName;
70
71
72 private BlockDisk dataFile;
73
74
75 private final BlockDiskCacheAttributes blockDiskCacheAttributes;
76
77
78 private final File rootDirectory;
79
80
81 private BlockDiskKeyStore<K> keyStore;
82
83
84
85
86
87 private final ReentrantReadWriteLock storageLock = new ReentrantReadWriteLock();
88
89 private ScheduledFuture<?> future;
90
91
92
93
94
95
96 public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes )
97 {
98 this( cacheAttributes, null );
99 }
100
101
102
103
104
105
106
107 public BlockDiskCache( BlockDiskCacheAttributes cacheAttributes, IElementSerializer elementSerializer )
108 {
109 super( cacheAttributes );
110 setElementSerializer( elementSerializer );
111
112 this.blockDiskCacheAttributes = cacheAttributes;
113 this.logCacheName = "Region [" + getCacheName() + "] ";
114
115 if ( log.isInfoEnabled() )
116 {
117 log.info( logCacheName + "Constructing BlockDiskCache with attributes " + cacheAttributes );
118 }
119
120
121 this.fileName = getCacheName().replaceAll("[^a-zA-Z0-9-_\\.]", "_");
122 this.rootDirectory = cacheAttributes.getDiskPath();
123
124 if ( log.isInfoEnabled() )
125 {
126 log.info( logCacheName + "Cache file root directory: [" + rootDirectory + "]");
127 }
128
129 try
130 {
131 if ( this.blockDiskCacheAttributes.getBlockSizeBytes() > 0 )
132 {
133 this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
134 this.blockDiskCacheAttributes.getBlockSizeBytes(),
135 getElementSerializer() );
136 }
137 else
138 {
139 this.dataFile = new BlockDisk( new File( rootDirectory, fileName + ".data" ),
140 getElementSerializer() );
141 }
142
143 keyStore = new BlockDiskKeyStore<K>( this.blockDiskCacheAttributes, this );
144
145 boolean alright = verifyDisk();
146
147 if ( keyStore.size() == 0 || !alright )
148 {
149 this.reset();
150 }
151
152
153 setAlive(true);
154 if ( log.isInfoEnabled() )
155 {
156 log.info( logCacheName + "Block Disk Cache is alive." );
157 }
158 }
159 catch ( IOException e )
160 {
161 log.error( logCacheName + "Failure initializing for fileName: " + fileName + " and root directory: "
162 + rootDirectory, e );
163 }
164 }
165
166
167
168
169 @Override
170 public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutor)
171 {
172
173
174 if ( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() > 0 )
175 {
176 future = scheduledExecutor.scheduleAtFixedRate(
177 new Runnable()
178 {
179 @Override
180 public void run()
181 {
182 keyStore.saveKeys();
183 }
184 },
185 this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
186 this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds(),
187 TimeUnit.SECONDS);
188 }
189 }
190
191
192
193
194
195
196
197 protected boolean verifyDisk()
198 {
199 boolean alright = false;
200
201
202
203 storageLock.readLock().lock();
204
205 try
206 {
207 int maxToTest = 100;
208 int count = 0;
209 Iterator<Map.Entry<K, int[]>> it = this.keyStore.entrySet().iterator();
210 while ( it.hasNext() && count < maxToTest )
211 {
212 count++;
213 Map.Entry<K, int[]> entry = it.next();
214 Object data = this.dataFile.read( entry.getValue() );
215 if ( data == null )
216 {
217 throw new Exception( logCacheName + "Couldn't find data for key [" + entry.getKey() + "]" );
218 }
219 }
220 alright = true;
221 }
222 catch ( Exception e )
223 {
224 log.warn( logCacheName + "Problem verifying disk. Message [" + e.getMessage() + "]" );
225 alright = false;
226 }
227 finally
228 {
229 storageLock.readLock().unlock();
230 }
231
232 return alright;
233 }
234
235
236
237
238
239
240 @Override
241 public Set<K> getKeySet() throws IOException
242 {
243 HashSet<K> keys = new HashSet<K>();
244
245 storageLock.readLock().lock();
246
247 try
248 {
249 keys.addAll(this.keyStore.keySet());
250 }
251 finally
252 {
253 storageLock.readLock().unlock();
254 }
255
256 return keys;
257 }
258
259
260
261
262
263
264
265
266 @Override
267 public Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
268 {
269 Map<K, ICacheElement<K, V>> elements = new HashMap<K, ICacheElement<K, V>>();
270
271 Set<K> keyArray = null;
272 storageLock.readLock().lock();
273 try
274 {
275 keyArray = new HashSet<K>(keyStore.keySet());
276 }
277 finally
278 {
279 storageLock.readLock().unlock();
280 }
281
282 Set<K> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
283
284 for (K key : matchingKeys)
285 {
286 ICacheElement<K, V> element = processGet( key );
287 if ( element != null )
288 {
289 elements.put( key, element );
290 }
291 }
292
293 return elements;
294 }
295
296
297
298
299
300
301
302 @Override
303 public int getSize()
304 {
305 return this.keyStore.size();
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319 @Override
320 protected ICacheElement<K, V> processGet( K key )
321 {
322 if ( !isAlive() )
323 {
324 if ( log.isDebugEnabled() )
325 {
326 log.debug( logCacheName + "No longer alive so returning null for key = " + key );
327 }
328 return null;
329 }
330
331 if ( log.isDebugEnabled() )
332 {
333 log.debug( logCacheName + "Trying to get from disk: " + key );
334 }
335
336 ICacheElement<K, V> object = null;
337
338
339 try
340 {
341 storageLock.readLock().lock();
342 try {
343 int[] ded = this.keyStore.get( key );
344 if ( ded != null )
345 {
346 object = this.dataFile.read( ded );
347 }
348 } finally {
349 storageLock.readLock().unlock();
350 }
351
352 }
353 catch ( IOException ioe )
354 {
355 log.error( logCacheName + "Failure getting from disk--IOException, key = " + key, ioe );
356 reset();
357 }
358 catch ( Exception e )
359 {
360 log.error( logCacheName + "Failure getting from disk, key = " + key, e );
361 }
362 return object;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376 @Override
377 protected void processUpdate( ICacheElement<K, V> element )
378 {
379 if ( !isAlive() )
380 {
381 if ( log.isDebugEnabled() )
382 {
383 log.debug( logCacheName + "No longer alive; aborting put of key = " + element.getKey() );
384 }
385 return;
386 }
387
388 int[] old = null;
389
390
391 storageLock.writeLock().lock();
392
393 try
394 {
395 old = this.keyStore.get( element.getKey() );
396
397 if ( old != null )
398 {
399 this.dataFile.freeBlocks( old );
400 }
401
402 int[] blocks = this.dataFile.write( element );
403
404 this.keyStore.put( element.getKey(), blocks );
405
406 if ( log.isDebugEnabled() )
407 {
408 log.debug( logCacheName + "Put to file [" + fileName + "] key [" + element.getKey() + "]" );
409 }
410 }
411 catch ( IOException e )
412 {
413 log.error( logCacheName + "Failure updating element, key: " + element.getKey() + " old: " + Arrays.toString(old), e );
414 }
415 finally
416 {
417 storageLock.writeLock().unlock();
418 }
419
420 if ( log.isDebugEnabled() )
421 {
422 log.debug( logCacheName + "Storing element on disk, key: " + element.getKey() );
423 }
424 }
425
426
427
428
429
430
431
432
433
434 @Override
435 protected boolean processRemove( K key )
436 {
437 if ( !isAlive() )
438 {
439 if ( log.isDebugEnabled() )
440 {
441 log.debug( logCacheName + "No longer alive so returning false for key = " + key );
442 }
443 return false;
444 }
445
446 boolean reset = false;
447 boolean removed = false;
448
449 storageLock.writeLock().lock();
450
451 try
452 {
453 if (key instanceof String && key.toString().endsWith(CacheConstants.NAME_COMPONENT_DELIMITER))
454 {
455 removed = performPartialKeyRemoval((String) key);
456 }
457 else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
458 {
459 removed = performGroupRemoval(((GroupAttrName<?>) key).groupId);
460 }
461 else
462 {
463 removed = performSingleKeyRemoval(key);
464 }
465 }
466 catch ( Exception e )
467 {
468 log.error( logCacheName + "Problem removing element.", e );
469 reset = true;
470 }
471 finally
472 {
473 storageLock.writeLock().unlock();
474 }
475
476 if ( reset )
477 {
478 reset();
479 }
480
481 return removed;
482 }
483
484
485
486
487
488
489
490
491
492
493
494 private boolean performGroupRemoval(GroupId key)
495 {
496 boolean removed = false;
497
498
499 List<K> itemsToRemove = new LinkedList<K>();
500
501
502 for (K k : keyStore.keySet())
503 {
504 if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(key))
505 {
506 itemsToRemove.add(k);
507 }
508 }
509
510
511 for (K fullKey : itemsToRemove)
512 {
513
514
515 performSingleKeyRemoval(fullKey);
516 removed = true;
517
518 }
519
520 return removed;
521 }
522
523
524
525
526
527
528
529
530
531
532
533 private boolean performPartialKeyRemoval(String key)
534 {
535 boolean removed = false;
536
537
538 List<K> itemsToRemove = new LinkedList<K>();
539
540 for (K k : keyStore.keySet())
541 {
542 if (k instanceof String && k.toString().startsWith(key))
543 {
544 itemsToRemove.add(k);
545 }
546 }
547
548
549 for (K fullKey : itemsToRemove)
550 {
551
552
553 performSingleKeyRemoval(fullKey);
554 removed = true;
555
556 }
557
558 return removed;
559 }
560
561
562 private boolean performSingleKeyRemoval(K key) {
563 boolean removed;
564
565 int[] ded = this.keyStore.remove( key );
566 removed = ded != null;
567 if ( removed )
568 {
569 this.dataFile.freeBlocks( ded );
570 }
571
572 if ( log.isDebugEnabled() )
573 {
574 log.debug( logCacheName + "Disk removal: Removed from key hash, key [" + key + "] removed = "
575 + removed );
576 }
577 return removed;
578 }
579
580
581
582
583
584
585 @Override
586 protected void processRemoveAll()
587 {
588 reset();
589 }
590
591
592
593
594
595
596
597 @Override
598 public void processDispose()
599 {
600 Runnable disR = new Runnable()
601 {
602 @Override
603 public void run()
604 {
605 try
606 {
607 disposeInternal();
608 }
609 catch ( InterruptedException e )
610 {
611 log.warn( "Interrupted while diposing." );
612 }
613 }
614 };
615 Thread t = new Thread( disR, "BlockDiskCache-DisposalThread" );
616 t.start();
617
618 try
619 {
620 t.join( 60 * 1000 );
621 }
622 catch ( InterruptedException ex )
623 {
624 log.error( logCacheName + "Interrupted while waiting for disposal thread to finish.", ex );
625 }
626 }
627
628
629
630
631
632 protected void disposeInternal()
633 throws InterruptedException
634 {
635 if ( !isAlive() )
636 {
637 log.error( logCacheName + "Not alive and dispose was called, filename: " + fileName );
638 return;
639 }
640 storageLock.writeLock().lock();
641 try
642 {
643
644 setAlive(false);
645 this.keyStore.saveKeys();
646
647 if (future != null)
648 {
649 future.cancel(true);
650 }
651
652 try
653 {
654 if ( log.isDebugEnabled() )
655 {
656 log.debug( logCacheName + "Closing files, base filename: " + fileName );
657 }
658 dataFile.close();
659
660
661
662
663
664 }
665 catch ( IOException e )
666 {
667 log.error( logCacheName + "Failure closing files in dispose, filename: " + fileName, e );
668 }
669 }
670 finally
671 {
672 storageLock.writeLock().unlock();
673 }
674
675 if ( log.isInfoEnabled() )
676 {
677 log.info( logCacheName + "Shutdown complete." );
678 }
679 }
680
681
682
683
684
685
686 @Override
687 public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
688 {
689 return this.blockDiskCacheAttributes;
690 }
691
692
693
694
695
696
697 private void reset()
698 {
699 if ( log.isWarnEnabled() )
700 {
701 log.warn( logCacheName + "Resetting cache" );
702 }
703
704 try
705 {
706 storageLock.writeLock().lock();
707
708 this.keyStore.reset();
709
710 if ( dataFile != null )
711 {
712 dataFile.reset();
713 }
714 }
715 catch ( IOException e )
716 {
717 log.error( logCacheName + "Failure resetting state", e );
718 }
719 finally
720 {
721 storageLock.writeLock().unlock();
722 }
723 }
724
725
726
727
728
729
730 protected void freeBlocks( int[] blocksToFree )
731 {
732 this.dataFile.freeBlocks( blocksToFree );
733 }
734
735
736
737
738
739
740 @Override
741 public IStats getStatistics()
742 {
743 IStats stats = new Stats();
744 stats.setTypeName( "Block Disk Cache" );
745
746 ArrayList<IStatElement<?>> elems = new ArrayList<IStatElement<?>>();
747
748 elems.add(new StatElement<Boolean>( "Is Alive", Boolean.valueOf(isAlive()) ) );
749 elems.add(new StatElement<Integer>( "Key Map Size", Integer.valueOf(this.keyStore.size()) ) );
750
751 if (this.dataFile != null)
752 {
753 try
754 {
755 elems.add(new StatElement<Long>( "Data File Length", Long.valueOf(this.dataFile.length()) ) );
756 }
757 catch ( IOException e )
758 {
759 log.error( e );
760 }
761
762 elems.add(new StatElement<Integer>( "Block Size Bytes",
763 Integer.valueOf(this.dataFile.getBlockSizeBytes()) ) );
764 elems.add(new StatElement<Integer>( "Number Of Blocks",
765 Integer.valueOf(this.dataFile.getNumberOfBlocks()) ) );
766 elems.add(new StatElement<Long>( "Average Put Size Bytes",
767 Long.valueOf(this.dataFile.getAveragePutSizeBytes()) ) );
768 elems.add(new StatElement<Integer>( "Empty Blocks",
769 Integer.valueOf(this.dataFile.getEmptyBlocks()) ) );
770 }
771
772
773 IStats sStats = super.getStatistics();
774 elems.addAll(sStats.getStatElements());
775
776 stats.setStatElements( elems );
777
778 return stats;
779 }
780
781
782
783
784
785
786 @Override
787 protected String getDiskLocation()
788 {
789 return dataFile.getFilePath();
790 }
791 }