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.BufferedInputStream;
23 import java.io.BufferedOutputStream;
24 import java.io.EOFException;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 import java.util.TreeMap;
37 import java.util.concurrent.atomic.AtomicInteger;
38
39 import org.apache.commons.jcs.auxiliary.disk.behavior.IDiskCacheAttributes.DiskLimitType;
40 import org.apache.commons.jcs.io.ObjectInputStreamClassLoaderAware;
41 import org.apache.commons.jcs.utils.struct.AbstractLRUMap;
42 import org.apache.commons.jcs.utils.struct.LRUMap;
43 import org.apache.commons.jcs.utils.timing.ElapsedTimer;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46
47
48
49
50
51
52
53 public class BlockDiskKeyStore<K>
54 {
55
56 private static final Log log = LogFactory.getLog(BlockDiskKeyStore.class);
57
58
59 private final BlockDiskCacheAttributes blockDiskCacheAttributes;
60
61
62 private Map<K, int[]> keyHash;
63
64
65 private final File keyFile;
66
67
68 protected final String logCacheName;
69
70
71 private final String fileName;
72
73
74 private final int maxKeySize;
75
76
77
78
79
80 protected final BlockDiskCache<K, ?> blockDiskCache;
81
82 private DiskLimitType diskLimitType = DiskLimitType.COUNT;
83
84 private int blockSize;
85
86
87
88
89
90
91
92
93
94 public BlockDiskKeyStore(BlockDiskCacheAttributes cacheAttributes, BlockDiskCache<K, ?> blockDiskCache)
95 {
96 this.blockDiskCacheAttributes = cacheAttributes;
97 this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] ";
98 this.fileName = this.blockDiskCacheAttributes.getCacheName();
99 this.maxKeySize = cacheAttributes.getMaxKeySize();
100 this.blockDiskCache = blockDiskCache;
101 this.diskLimitType = cacheAttributes.getDiskLimitType();
102 this.blockSize = cacheAttributes.getBlockSizeBytes();
103
104 File rootDirectory = cacheAttributes.getDiskPath();
105
106 if (log.isInfoEnabled())
107 {
108 log.info(logCacheName + "Cache file root directory [" + rootDirectory + "]");
109 }
110
111 this.keyFile = new File(rootDirectory, fileName + ".key");
112
113 if (log.isInfoEnabled())
114 {
115 log.info(logCacheName + "Key File [" + this.keyFile.getAbsolutePath() + "]");
116 }
117
118 if (keyFile.length() > 0)
119 {
120 loadKeys();
121 if (!verify())
122 {
123 log.warn(logCacheName + "Key File is invalid. Resetting file.");
124 initKeyMap();
125 reset();
126 }
127 }
128 else
129 {
130 initKeyMap();
131 }
132 }
133
134
135
136
137
138 protected void saveKeys()
139 {
140 try
141 {
142 ElapsedTimer timer = new ElapsedTimer();
143 int numKeys = keyHash.size();
144 if (log.isInfoEnabled())
145 {
146 log.info(logCacheName + "Saving keys to [" + this.keyFile.getAbsolutePath() + "], key count [" + numKeys + "]");
147 }
148
149 synchronized (keyFile)
150 {
151 FileOutputStream fos = new FileOutputStream(keyFile);
152 BufferedOutputStream bos = new BufferedOutputStream(fos, 65536);
153 ObjectOutputStream oos = new ObjectOutputStream(bos);
154 try
155 {
156 if (!verify())
157 {
158 throw new IOException("Inconsistent key file");
159 }
160
161
162 for (Map.Entry<K, int[]> entry : keyHash.entrySet())
163 {
164 BlockDiskElementDescriptor<K> descriptor = new BlockDiskElementDescriptor<K>();
165 descriptor.setKey(entry.getKey());
166 descriptor.setBlocks(entry.getValue());
167
168 oos.writeUnshared(descriptor);
169 }
170 }
171 finally
172 {
173 oos.flush();
174 oos.close();
175 }
176 }
177
178 if (log.isInfoEnabled())
179 {
180 log.info(logCacheName + "Finished saving keys. It took " + timer.getElapsedTimeString() + " to store " + numKeys
181 + " keys. Key file length [" + keyFile.length() + "]");
182 }
183 }
184 catch (IOException e)
185 {
186 log.error(logCacheName + "Problem storing keys.", e);
187 }
188 }
189
190
191
192
193 protected void reset()
194 {
195 synchronized (keyFile)
196 {
197 clearMemoryMap();
198 saveKeys();
199 }
200 }
201
202
203
204
205
206 protected void clearMemoryMap()
207 {
208 this.keyHash.clear();
209 }
210
211
212
213
214 private void initKeyMap()
215 {
216 keyHash = null;
217 if (maxKeySize >= 0)
218 {
219 if (this.diskLimitType == DiskLimitType.SIZE)
220 {
221 keyHash = new LRUMapSizeLimited(maxKeySize);
222 }
223 else
224 {
225 keyHash = new LRUMapCountLimited(maxKeySize);
226 }
227 if (log.isInfoEnabled())
228 {
229 log.info(logCacheName + "Set maxKeySize to: '" + maxKeySize + "'");
230 }
231 }
232 else
233 {
234
235
236 keyHash = new HashMap<K, int[]>();
237
238 if (log.isInfoEnabled())
239 {
240 log.info(logCacheName + "Set maxKeySize to unlimited'");
241 }
242 }
243 }
244
245
246
247
248
249 protected void loadKeys()
250 {
251 if (log.isInfoEnabled())
252 {
253 log.info(logCacheName + "Loading keys for " + keyFile.toString());
254 }
255
256 try
257 {
258
259 initKeyMap();
260
261 HashMap<K, int[]> keys = new HashMap<K, int[]>();
262
263 synchronized (keyFile)
264 {
265 FileInputStream fis = new FileInputStream(keyFile);
266 BufferedInputStream bis = new BufferedInputStream(fis, 65536);
267 ObjectInputStream ois = new ObjectInputStreamClassLoaderAware(bis, null);
268 try
269 {
270 while (true)
271 {
272 @SuppressWarnings("unchecked")
273
274 BlockDiskElementDescriptor<K> descriptor = (BlockDiskElementDescriptor<K>) ois.readObject();
275 if (descriptor != null)
276 {
277 keys.put(descriptor.getKey(), descriptor.getBlocks());
278 }
279 }
280 }
281 catch (EOFException eof)
282 {
283
284 }
285 finally
286 {
287 ois.close();
288 }
289 }
290
291 if (!keys.isEmpty())
292 {
293 keyHash.putAll(keys);
294
295 if (log.isDebugEnabled())
296 {
297 log.debug(logCacheName + "Found " + keys.size() + " in keys file.");
298 }
299
300 if (log.isInfoEnabled())
301 {
302 log.info(logCacheName + "Loaded keys from [" + fileName + "], key count: " + keyHash.size() + "; up to "
303 + maxKeySize + " will be available.");
304 }
305 }
306 }
307 catch (Exception e)
308 {
309 log.error(logCacheName + "Problem loading keys for file " + fileName, e);
310 }
311 }
312
313
314
315
316
317
318
319 public Set<Map.Entry<K, int[]>> entrySet()
320 {
321 return this.keyHash.entrySet();
322 }
323
324
325
326
327
328
329
330 public Set<K> keySet()
331 {
332 return this.keyHash.keySet();
333 }
334
335
336
337
338
339
340
341 public int size()
342 {
343 return this.keyHash.size();
344 }
345
346
347
348
349
350
351
352
353 public int[] get(K key)
354 {
355 return this.keyHash.get(key);
356 }
357
358
359
360
361
362
363
364
365 public void put(K key, int[] value)
366 {
367 this.keyHash.put(key, value);
368 }
369
370
371
372
373
374
375
376
377 public int[] remove(K key)
378 {
379 return this.keyHash.remove(key);
380 }
381
382
383
384
385
386
387 private boolean verify()
388 {
389 Map<Integer, Set<K>> blockAllocationMap = new TreeMap<Integer, Set<K>>();
390 for (Entry<K, int[]> e : keyHash.entrySet())
391 {
392 for (int block : e.getValue())
393 {
394 Set<K> keys = blockAllocationMap.get(block);
395 if (keys == null)
396 {
397 keys = new HashSet<K>();
398 blockAllocationMap.put(block, keys);
399 }
400 else if (!log.isDebugEnabled())
401 {
402
403 return false;
404 }
405 keys.add(e.getKey());
406 }
407 }
408 boolean ok = true;
409 if (log.isDebugEnabled())
410 {
411 for (Entry<Integer, Set<K>> e : blockAllocationMap.entrySet())
412 {
413 log.debug("Block " + e.getKey() + ":" + e.getValue());
414 if (e.getValue().size() > 1)
415 {
416 ok = false;
417 }
418 }
419 return ok;
420 }
421 else
422 {
423 return ok;
424 }
425 }
426
427
428
429
430
431 public class LRUMapSizeLimited extends AbstractLRUMap<K, int[]>
432 {
433
434
435
436 public final static String TAG = "orig-lru-size";
437
438
439 private AtomicInteger contentSize;
440 private int maxSize;
441
442
443
444
445 public LRUMapSizeLimited()
446 {
447 this(-1);
448 }
449
450
451
452
453
454 public LRUMapSizeLimited(int maxSize)
455 {
456 super();
457 this.maxSize = maxSize;
458 this.contentSize = new AtomicInteger(0);
459 }
460
461
462 private void subLengthFromCacheSize(int[] value)
463 {
464 contentSize.addAndGet(value.length * blockSize / -1024 - 1);
465 }
466
467
468 private void addLengthToCacheSize(int[] value)
469 {
470 contentSize.addAndGet(value.length * blockSize / 1024 + 1);
471 }
472
473 @Override
474 public int[] put(K key, int[] value)
475 {
476 int[] oldValue = null;
477
478 try
479 {
480 oldValue = super.put(key, value);
481 }
482 finally
483 {
484 if (value != null)
485 {
486 addLengthToCacheSize(value);
487 }
488 if (oldValue != null)
489 {
490 subLengthFromCacheSize(oldValue);
491 }
492 }
493
494 return oldValue;
495 }
496
497 @Override
498 public int[] remove(Object key)
499 {
500 int[] value = null;
501
502 try
503 {
504 value = super.remove(key);
505 return value;
506 }
507 finally
508 {
509 if (value != null)
510 {
511 subLengthFromCacheSize(value);
512 }
513 }
514 }
515
516
517
518
519
520
521
522
523
524
525 @Override
526 protected void processRemovedLRU(K key, int[] value)
527 {
528 blockDiskCache.freeBlocks(value);
529 if (log.isDebugEnabled())
530 {
531 log.debug(logCacheName + "Removing key: [" + key + "] from key store.");
532 log.debug(logCacheName + "Key store size: [" + super.size() + "].");
533 }
534
535 if (value != null)
536 {
537 subLengthFromCacheSize(value);
538 }
539 }
540
541 @Override
542 protected boolean shouldRemove()
543 {
544 return maxSize > 0 && contentSize.get() > maxSize && this.size() > 1;
545 }
546 }
547
548
549
550
551
552 public class LRUMapCountLimited extends LRUMap<K, int[]>
553 {
554
555
556
557 public final static String TAG = "orig-lru-count";
558
559 public LRUMapCountLimited(int maxKeySize)
560 {
561 super(maxKeySize);
562 }
563
564
565
566
567
568
569
570
571
572
573 @Override
574 protected void processRemovedLRU(K key, int[] value)
575 {
576 blockDiskCache.freeBlocks(value);
577 if (log.isDebugEnabled())
578 {
579 log.debug(logCacheName + "Removing key: [" + key + "] from key store.");
580 log.debug(logCacheName + "Key store size: [" + super.size() + "].");
581 }
582 }
583 }
584 }