1 package org.apache.jcs.auxiliary.disk.file;
2
3 import java.io.BufferedOutputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.io.Serializable;
11 import java.util.Map;
12 import java.util.Set;
13
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16 import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
17 import org.apache.jcs.auxiliary.disk.AbstractDiskCache;
18 import org.apache.jcs.engine.behavior.ICacheElement;
19 import org.apache.jcs.engine.behavior.IElementSerializer;
20 import org.apache.jcs.engine.logging.behavior.ICacheEvent;
21 import org.apache.jcs.engine.logging.behavior.ICacheEventLogger;
22 import org.apache.jcs.utils.timing.SleepUtil;
23
24
25
26
27
28
29
30
31 public class FileDiskCache<K extends Serializable, V extends Serializable>
32 extends AbstractDiskCache<K, V>
33 {
34
35 private static final long serialVersionUID = 1L;
36
37
38 private static final Log log = LogFactory.getLog( FileDiskCache.class );
39
40
41 private final String logCacheName;
42
43
44 private final FileDiskCacheAttributes diskFileCacheAttributes;
45
46
47 private File directory;
48
49
50
51
52
53
54 public FileDiskCache( FileDiskCacheAttributes cacheAttributes )
55 {
56 this( cacheAttributes, null );
57 }
58
59
60
61
62
63
64
65
66 public FileDiskCache( FileDiskCacheAttributes cattr, IElementSerializer elementSerializer )
67 {
68 super( cattr );
69 setElementSerializer( elementSerializer );
70 this.diskFileCacheAttributes = cattr;
71 this.logCacheName = "Region [" + getCacheName() + "] ";
72 alive = initializeFileSystem( cattr );
73 }
74
75
76
77
78
79
80
81 private boolean initializeFileSystem( FileDiskCacheAttributes cattr )
82 {
83
84 String rootDirName = cattr.getDiskPath() + "/" + cattr.getCacheName();
85 this.setDirectory( new File( rootDirName ) );
86 boolean createdDirectories = getDirectory().mkdirs();
87 if ( log.isInfoEnabled() )
88 {
89 log.info( logCacheName + "Cache file root directory: " + rootDirName );
90 log.info( logCacheName + "Created root directory: " + createdDirectories );
91 }
92
93
94 boolean exists = getDirectory().exists();
95 if ( !exists )
96 {
97 log.error( "Could not initialize File Disk Cache. The root directory does not exist." );
98 }
99 return exists;
100 }
101
102
103
104
105
106
107
108
109
110
111 protected <KK extends Serializable> File file( KK key )
112 {
113 StringBuffer fileNameBuffer = new StringBuffer();
114
115
116 String keys = key.toString();
117 int l = keys.length();
118 for ( int i = 0; i < l; i++ )
119 {
120 char c = keys.charAt( i );
121 if ( !Character.isLetterOrDigit( c ) )
122 {
123 c = '_';
124 }
125 fileNameBuffer.append( c );
126 }
127 String fileName = fileNameBuffer.toString();
128
129 if ( log.isDebugEnabled() )
130 {
131 log.debug( logCacheName + "Creating file for name: [" + fileName + "] based on key: [" + key + "]" );
132 }
133
134 return new File( getDirectory().getAbsolutePath(), fileName );
135 }
136
137
138
139
140
141
142
143 @Override
144 public Set<K> getGroupKeys(String groupName)
145 {
146 throw new UnsupportedOperationException();
147 }
148
149
150
151
152
153
154 @Override
155 public Set<String> getGroupNames()
156 {
157 throw new UnsupportedOperationException();
158 }
159
160
161
162
163 @Override
164 public int getSize()
165 {
166 if ( getDirectory().exists() )
167 {
168 return getDirectory().list().length;
169 }
170 return 0;
171 }
172
173
174
175
176 public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
177 {
178 return diskFileCacheAttributes;
179 }
180
181
182
183
184 @Override
185 protected String getDiskLocation()
186 {
187 return getDirectory().getAbsolutePath();
188 }
189
190
191
192
193
194
195 @Override
196 protected synchronized void processDispose()
197 throws IOException
198 {
199 ICacheEvent<String> cacheEvent = createICacheEvent( cacheName, "none", ICacheEventLogger.DISPOSE_EVENT );
200 try
201 {
202 if ( !alive )
203 {
204 log.error( logCacheName + "Not alive and dispose was called, directgory: " + getDirectory() );
205 return;
206 }
207
208
209 alive = false;
210
211
212 if ( log.isInfoEnabled() )
213 {
214 log.info( logCacheName + "Shutdown complete." );
215 }
216 }
217 finally
218 {
219 logICacheEvent( cacheEvent );
220 }
221 }
222
223
224
225
226
227
228
229
230 @Override
231 protected ICacheElement<K, V> processGet( K key )
232 throws IOException
233 {
234 File file = file( key );
235
236 if ( !file.exists() )
237 {
238 if ( log.isDebugEnabled() )
239 {
240 log.debug( "File does not exist. Returning null from Get." + file );
241 }
242 return null;
243 }
244
245 ICacheElement<K, V> element = null;
246
247 FileInputStream fis = null;
248 try
249 {
250 fis = new FileInputStream( file );
251
252 long length = file.length();
253
254 byte[] bytes = new byte[(int) length];
255
256 int offset = 0;
257 int numRead = 0;
258 while ( offset < bytes.length && ( numRead = fis.read( bytes, offset, bytes.length - offset ) ) >= 0 )
259 {
260 offset += numRead;
261 }
262
263
264 if ( offset < bytes.length )
265 {
266 throw new IOException( "Could not completely read file " + file.getName() );
267 }
268
269 element = getElementSerializer().deSerialize( bytes );
270
271
272 if ( element != null && !key.equals( element.getKey() ) )
273 {
274 if ( log.isInfoEnabled() )
275 {
276 log.info( logCacheName + "key: [" + key + "] point to cached object with key: [" + element.getKey()
277 + "]" );
278 }
279 element = null;
280 }
281 }
282 catch ( IOException e )
283 {
284 log.error( logCacheName + "Failure getting element, key: [" + key + "]", e );
285 }
286 catch ( ClassNotFoundException e )
287 {
288 log.error( logCacheName + "Failure getting element, key: [" + key + "]", e );
289 }
290 finally
291 {
292 silentClose( fis );
293 }
294
295
296 if ( element != null && diskFileCacheAttributes.isTouchOnGet() )
297 {
298 touchWithRetry( file );
299 }
300 return element;
301 }
302
303
304
305
306
307
308 @Override
309 protected Map<K, ICacheElement<K, V>> processGetMatching( String pattern )
310 throws IOException
311 {
312
313
314 return null;
315 }
316
317
318
319
320
321
322
323
324 @Override
325 protected boolean processRemove( K key )
326 throws IOException
327 {
328 return _processRemove(key);
329 }
330
331
332
333
334
335
336
337
338 private <T extends Serializable> boolean _processRemove( T key )
339 throws IOException
340 {
341 File file = file( key );
342 if ( log.isDebugEnabled() )
343 {
344 log.debug( logCacheName + "Removing file " + file );
345 }
346 return deleteWithRetry( file );
347 }
348
349
350
351
352
353
354
355
356
357 @Override
358 protected void processRemoveAll()
359 throws IOException
360 {
361 String[] fileNames = getDirectory().list();
362 for ( int i = 0; i < fileNames.length; i++ )
363 {
364 _processRemove( fileNames[i] );
365 }
366 }
367
368
369
370
371
372
373
374
375 @Override
376 protected void processUpdate( ICacheElement<K, V> element )
377 throws IOException
378 {
379 removeIfLimitIsSetAndReached();
380
381 File file = file( element.getKey() );
382
383 File tmp = null;
384 OutputStream os = null;
385 try
386 {
387 byte[] bytes = getElementSerializer().serialize( element );
388
389 tmp = File.createTempFile( "JCS_DiskFileCache", null, getDirectory() );
390
391 FileOutputStream fos = new FileOutputStream( tmp );
392 os = new BufferedOutputStream( fos );
393
394 if ( bytes != null )
395 {
396 if ( log.isDebugEnabled() )
397 {
398 log.debug( logCacheName + "Wrote " + bytes.length + " bytes to file " + tmp );
399 }
400 os.write( bytes );
401 os.close();
402 }
403 deleteWithRetry( file );
404 tmp.renameTo( file );
405 if ( log.isDebugEnabled() )
406 {
407 log.debug( logCacheName + "Renamed to: " + file );
408 }
409 }
410 catch ( IOException e )
411 {
412 log.error( logCacheName + "Failure updating element, key: [" + element.getKey() + "]", e );
413 }
414 finally
415 {
416 silentClose( os );
417 if ( ( tmp != null ) && tmp.exists() )
418 {
419 deleteWithRetry( tmp );
420 }
421 }
422 }
423
424
425
426
427
428
429
430 private void removeIfLimitIsSetAndReached()
431 {
432 if ( diskFileCacheAttributes.getMaxNumberOfFiles() > 0 )
433 {
434
435 if ( getSize() >= diskFileCacheAttributes.getMaxNumberOfFiles() )
436 {
437 if ( log.isDebugEnabled() )
438 {
439 log.debug( logCacheName + "Max reached, removing least recently modifed" );
440 }
441
442 long oldestLastModified = System.currentTimeMillis();
443 File theLeastRecentlyModified = null;
444 String[] fileNames = getDirectory().list();
445 for ( int i = 0; i < fileNames.length; i++ )
446 {
447 File file = file( fileNames[i] );
448 long lastModified = file.lastModified();
449 if ( lastModified < oldestLastModified )
450 {
451 oldestLastModified = lastModified;
452 theLeastRecentlyModified = file;
453 }
454 }
455 if ( theLeastRecentlyModified != null )
456 {
457 if ( log.isDebugEnabled() )
458 {
459 log.debug( logCacheName + "Least recently modifed: " + theLeastRecentlyModified );
460 }
461 deleteWithRetry( theLeastRecentlyModified );
462 }
463 }
464 }
465 }
466
467
468
469
470
471
472
473
474 private boolean deleteWithRetry( File file )
475 {
476 boolean success = file.delete();
477
478
479 if ( file.exists() )
480 {
481 int maxRetries = diskFileCacheAttributes.getMaxRetriesOnDelete();
482 for ( int i = 0; i < maxRetries && !success; i++ )
483 {
484 SleepUtil.sleepAtLeast( 5 );
485 success = file.delete();
486 }
487 }
488 else
489 {
490 success = true;
491 }
492 if ( log.isDebugEnabled() )
493 {
494 log.debug( logCacheName + "deleteWithRetry. success= " + success + " file: " + file );
495 }
496 return success;
497 }
498
499
500
501
502
503
504
505 private boolean touchWithRetry( File file )
506 {
507 boolean success = file.setLastModified( System.currentTimeMillis() );
508 if ( !success )
509 {
510 int maxRetries = diskFileCacheAttributes.getMaxRetriesOnTouch();
511 if ( file.exists() )
512 {
513 for ( int i = 0; i < maxRetries && !success; i++ )
514 {
515 SleepUtil.sleepAtLeast( 5 );
516 success = file.delete();
517 }
518 }
519 }
520 if ( log.isDebugEnabled() )
521 {
522 log.debug( logCacheName + "Last modified, success: " + success );
523 }
524 return success;
525 }
526
527
528
529
530
531
532 private void silentClose( InputStream s )
533 {
534 if ( s != null )
535 {
536 try
537 {
538 s.close();
539 }
540 catch ( IOException e )
541 {
542 log.error( logCacheName + "Failure closing stream", e );
543 }
544 }
545 }
546
547
548
549
550
551
552 private void silentClose( OutputStream s )
553 {
554 if ( s != null )
555 {
556 try
557 {
558 s.close();
559 }
560 catch ( IOException e )
561 {
562 log.error( logCacheName + "Failure closing stream", e );
563 }
564 }
565 }
566
567
568
569
570 protected void setDirectory( File directory )
571 {
572 this.directory = directory;
573 }
574
575
576
577
578 protected File getDirectory()
579 {
580 return directory;
581 }
582 }