View Javadoc

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