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.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.ObjectInputStream;
29 import java.io.ObjectOutputStream;
30 import java.io.Serializable;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.jcs.auxiliary.disk.LRUMapJCS;
39 import org.apache.jcs.utils.timing.ElapsedTimer;
40
41 import EDU.oswego.cs.dl.util.concurrent.ClockDaemon;
42 import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
43
44 /***
45 * This is responsible for storing the keys.
46 * <p>
47 * @author Aaron Smuts
48 */
49 public class BlockDiskKeyStore
50 {
51 /*** The logger */
52 private static final Log log = LogFactory.getLog( BlockDiskKeyStore.class );
53
54 /*** Attributes governing the behavior of the block disk cache. */
55 private BlockDiskCacheAttributes blockDiskCacheAttributes;
56
57 /*** The key to block map */
58 private Map keyHash;
59
60 /*** The file where we persist the keys */
61 private File keyFile;
62
63 /*** The name to prefix log messages with. */
64 private final String logCacheName;
65
66 /*** Name of the file where we persist the keys */
67 private String fileName;
68
69 /*** The maximum number of keys to store in memory */
70 private int maxKeySize;
71
72 /*** we need this so we can communicate free blocks to the data store when keys fall off the LRU */
73 private BlockDiskCache blockDiskCache;
74
75 /*** The root directory in which the keyFile lives */
76 private File rootDirectory;
77
78 /***
79 * The background key persister, one for all regions.
80 */
81 private static ClockDaemon persistenceDaemon;
82
83 /***
84 * Set the configuration options.
85 * <p>
86 * @param cacheAttributes
87 * @param blockDiskCache used for freeing
88 * @throws Exception
89 */
90 public BlockDiskKeyStore( BlockDiskCacheAttributes cacheAttributes, BlockDiskCache blockDiskCache )
91 throws Exception
92 {
93 this.blockDiskCacheAttributes = cacheAttributes;
94 this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] ";
95 this.fileName = this.blockDiskCacheAttributes.getCacheName();
96 this.maxKeySize = cacheAttributes.getMaxKeySize();
97 this.blockDiskCache = blockDiskCache;
98
99 String rootDirName = cacheAttributes.getDiskPath();
100 this.rootDirectory = new File( rootDirName );
101 this.rootDirectory.mkdirs();
102
103 if ( log.isInfoEnabled() )
104 {
105 log.info( logCacheName + "Cache file root directory [" + rootDirName + "]" );
106 }
107
108 this.keyFile = new File( rootDirectory, fileName + ".key" );
109
110 if ( log.isInfoEnabled() )
111 {
112 log.info( logCacheName + "Key File [" + this.keyFile.getAbsolutePath() + "]" );
113 }
114
115 if ( keyFile.length() > 0 )
116 {
117 loadKeys();
118
119 }
120 else
121 {
122 initKeyMap();
123 }
124
125
126
127 if ( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() > 0 )
128 {
129 if ( persistenceDaemon == null )
130 {
131 persistenceDaemon = new ClockDaemon();
132 persistenceDaemon.setThreadFactory( new MyThreadFactory() );
133 }
134 persistenceDaemon
135 .executePeriodically( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() * 1000,
136 new Runnable()
137 {
138 public void run()
139 {
140 saveKeys();
141 }
142 }, false );
143 }
144 }
145
146 /***
147 * Saves key file to disk. This gets the LRUMap entry set and write the entries out one by one
148 * after putting them in a wrapper.
149 */
150 protected void saveKeys()
151 {
152 try
153 {
154 ElapsedTimer timer = new ElapsedTimer();
155 int numKeys = keyHash.size();
156 if ( log.isInfoEnabled() )
157 {
158 log.info( logCacheName + "Saving keys to [" + this.keyFile.getAbsolutePath() + "], key count ["
159 + numKeys + "]" );
160 }
161
162 keyFile.delete();
163
164 keyFile = new File( rootDirectory, fileName + ".key" );
165 FileOutputStream fos = new FileOutputStream( keyFile );
166 BufferedOutputStream bos = new BufferedOutputStream( fos, 1024 );
167 ObjectOutputStream oos = new ObjectOutputStream( bos );
168 try
169 {
170
171 Iterator keyIt = keyHash.entrySet().iterator();
172 while ( keyIt.hasNext() )
173 {
174 Map.Entry entry = (Map.Entry) keyIt.next();
175 BlockDiskElementDescriptor descriptor = new BlockDiskElementDescriptor();
176 descriptor.setKey( (Serializable) entry.getKey() );
177 descriptor.setBlocks( (int[]) entry.getValue() );
178
179 oos.writeObject( descriptor );
180 }
181 }
182 finally
183 {
184 oos.flush();
185 oos.close();
186 }
187
188 if ( log.isInfoEnabled() )
189 {
190 log.info( logCacheName + "Finished saving keys. It took " + timer.getElapsedTimeString() + " to store "
191 + numKeys + " keys. Key file length [" + keyFile.length() + "]" );
192 }
193 }
194 catch ( Exception e )
195 {
196 log.error( logCacheName + "Problem storing keys.", e );
197 }
198 }
199
200 /***
201 * Resets the file and creates a new key map.
202 */
203 protected void reset()
204 {
205 File keyFileTemp = new File( this.rootDirectory, fileName + ".key" );
206 keyFileTemp.delete();
207
208 keyFile = new File( this.rootDirectory, fileName + ".key" );
209
210 initKeyMap();
211 }
212
213 /***
214 * This is mainly used for testing. It leave the disk in tact, and just clears memory.
215 */
216 protected void clearMemoryMap()
217 {
218 this.keyHash.clear();
219 }
220
221 /***
222 * Create the map for keys that contain the index position on disk.
223 */
224 private void initKeyMap()
225 {
226 keyHash = null;
227 if ( maxKeySize >= 0 )
228 {
229 keyHash = new LRUMap( maxKeySize );
230 if ( log.isInfoEnabled() )
231 {
232 log.info( logCacheName + "Set maxKeySize to: '" + maxKeySize + "'" );
233 }
234 }
235 else
236 {
237
238 keyHash = new HashMap();
239
240 if ( log.isInfoEnabled() )
241 {
242 log.info( logCacheName + "Set maxKeySize to unlimited'" );
243 }
244 }
245 }
246
247 /***
248 * Loads the keys from the .key file. The keys are stored individually on disk. They are added
249 * one by one to an LRUMap..
250 * <p>
251 * @throws InterruptedException
252 */
253 protected void loadKeys()
254 throws InterruptedException
255 {
256 if ( log.isInfoEnabled() )
257 {
258 log.info( logCacheName + "Loading keys for " + keyFile.toString() );
259 }
260
261 try
262 {
263
264 initKeyMap();
265
266 HashMap keys = new HashMap();
267
268 FileInputStream fis = new FileInputStream( keyFile );
269 BufferedInputStream bis = new BufferedInputStream( fis );
270 ObjectInputStream ois = new ObjectInputStream( bis );
271 try
272 {
273 while ( true )
274 {
275 BlockDiskElementDescriptor descriptor = (BlockDiskElementDescriptor) ois.readObject();
276 if ( descriptor != null )
277 {
278 keys.put( descriptor.getKey(), descriptor.getBlocks() );
279 }
280 }
281 }
282 catch ( EOFException eof )
283 {
284
285 }
286 finally
287 {
288 ois.close();
289 }
290
291 if ( !keys.isEmpty() )
292 {
293 if ( log.isDebugEnabled() )
294 {
295 log.debug( logCacheName + "Found " + keys.size() + " in keys file." );
296 }
297
298 keyHash.putAll( keys );
299
300 if ( log.isInfoEnabled() )
301 {
302 log.info( logCacheName + "Loaded keys from [" + fileName + "], key count: " + keyHash.size()
303 + "; up to " + 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 * Gets the entry set.
315 * <p>
316 * @return entry set.
317 */
318 public Set entrySet()
319 {
320 return this.keyHash.entrySet();
321 }
322
323 /***
324 * Gets the key set.
325 * <p>
326 * @return key set.
327 */
328 public Set keySet()
329 {
330 return this.keyHash.keySet();
331 }
332
333 /***
334 * Gets the size of the key hash.
335 * <p>
336 * @return the number of keys.
337 */
338 public int size()
339 {
340 return this.keyHash.size();
341 }
342
343 /***
344 * gets the object for the key.
345 * <p>
346 * @param key
347 * @return Object
348 */
349 public int[] get( Object key )
350 {
351 return (int[]) this.keyHash.get( key );
352 }
353
354 /***
355 * Puts a int[] in the keyStore.
356 * <p>
357 * @param key
358 * @param value
359 */
360 public void put( Object key, int[] value )
361 {
362 this.keyHash.put( key, value );
363 }
364
365 /***
366 * Remove by key.
367 * <p>
368 * @param key
369 * @return BlockDiskElementDescriptor if it was present, else null
370 */
371 public int[] remove( Object key )
372 {
373 return (int[]) this.keyHash.remove( key );
374 }
375
376 /***
377 * Class for recylcing and lru. This implments the LRU overflow callback, so we can mark the
378 * blocks as free.
379 */
380 public class LRUMap
381 extends LRUMapJCS
382 {
383 /*** Don't change */
384 private static final long serialVersionUID = 4955079991472142198L;
385
386 /***
387 * <code>tag</code> tells us which map we are working on.
388 */
389 public String tag = "orig";
390
391 /***
392 * Default
393 */
394 public LRUMap()
395 {
396 super();
397 }
398
399 /***
400 * @param maxKeySize
401 */
402 public LRUMap( int maxKeySize )
403 {
404 super( maxKeySize );
405 }
406
407 /***
408 * This is called when the may key size is reaced. The least recently used item will be
409 * passed here. We will store the position and size of the spot on disk in the recycle bin.
410 * <p>
411 * @param key
412 * @param value
413 */
414 protected void processRemovedLRU( Object key, Object value )
415 {
416 blockDiskCache.freeBlocks( (int[]) value );
417 if ( log.isDebugEnabled() )
418 {
419 log.debug( logCacheName + "Removing key: [" + key + "] from key store." );
420 log.debug( logCacheName + "Key store size: [" + this.size() + "]." );
421 }
422 }
423 }
424
425 /***
426 * Allows us to set the daemon status on the clockdaemon
427 * @author aaronsm
428 */
429 class MyThreadFactory
430 implements ThreadFactory
431 {
432
433 /***
434 * Ensures that we create daemon threads.
435 * <p>
436 * (non-Javadoc)
437 * @see EDU.oswego.cs.dl.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
438 */
439 public Thread newThread( Runnable runner )
440 {
441 Thread t = new Thread( runner );
442 t.setDaemon( true );
443 t.setPriority( Thread.MIN_PRIORITY );
444 return t;
445 }
446 }
447 }