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