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