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  
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  /**
42   * This is responsible for storing the keys.
43   * <p>
44   * @author Aaron Smuts
45   */
46  public class BlockDiskKeyStore<K extends Serializable>
47  {
48      /** The logger */
49      protected static final Log log = LogFactory.getLog( BlockDiskKeyStore.class );
50  
51      /** Attributes governing the behavior of the block disk cache. */
52      private final BlockDiskCacheAttributes blockDiskCacheAttributes;
53  
54      /** The key to block map */
55      private Map<K, int[]> keyHash;
56  
57      /** The file where we persist the keys */
58      private final File keyFile;
59  
60      /** The name to prefix log messages with. */
61      protected final String logCacheName;
62  
63      /** Name of the file where we persist the keys */
64      private final String fileName;
65  
66      /** The maximum number of keys to store in memory */
67      private final int maxKeySize;
68  
69      /** we need this so we can communicate free blocks to the data store when keys fall off the LRU */
70      protected final BlockDiskCache<K, ?> blockDiskCache;
71  
72      /**
73       * Set the configuration options.
74       * <p>
75       * @param cacheAttributes
76       * @param blockDiskCache used for freeing
77       */
78      public BlockDiskKeyStore( BlockDiskCacheAttributes cacheAttributes,
79              BlockDiskCache<K, ?> blockDiskCache)
80      {
81          this.blockDiskCacheAttributes = cacheAttributes;
82          this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] ";
83          this.fileName = this.blockDiskCacheAttributes.getCacheName();
84          this.maxKeySize = cacheAttributes.getMaxKeySize();
85          this.blockDiskCache = blockDiskCache;
86  
87          String rootDirName = cacheAttributes.getDiskPath();
88          File rootDirectory = new File( rootDirName );
89          rootDirectory.mkdirs();
90  
91          if ( log.isInfoEnabled() )
92          {
93              log.info( logCacheName + "Cache file root directory [" + rootDirName + "]" );
94          }
95  
96          this.keyFile = new File( rootDirectory, fileName + ".key" );
97  
98          if ( log.isInfoEnabled() )
99          {
100             log.info( logCacheName + "Key File [" + this.keyFile.getAbsolutePath() + "]" );
101         }
102 
103         if ( keyFile.length() > 0 )
104         {
105             loadKeys();
106             // TODO verify somehow
107         }
108         else
109         {
110             initKeyMap();
111         }
112     }
113 
114     /**
115      * Saves key file to disk. This gets the LRUMap entry set and write the entries out one by one
116      * after putting them in a wrapper.
117      */
118     protected void saveKeys()
119     {
120         try
121         {
122             ElapsedTimer timer = new ElapsedTimer();
123             int numKeys = keyHash.size();
124             if ( log.isInfoEnabled() )
125             {
126                 log.info( logCacheName + "Saving keys to [" + this.keyFile.getAbsolutePath() + "], key count ["
127                     + numKeys + "]" );
128             }
129 
130             synchronized (keyFile)
131             {
132                 FileOutputStream fos = new FileOutputStream( keyFile );
133                 BufferedOutputStream bos = new BufferedOutputStream( fos, 65536 );
134                 ObjectOutputStream oos = new ObjectOutputStream( bos );
135                 try
136                 {
137                     // don't need to synchronize, since the underlying collection makes a copy
138                     for (Map.Entry<K, int[]> entry : keyHash.entrySet())
139                     {
140                         BlockDiskElementDescriptor<K> descriptor = new BlockDiskElementDescriptor<K>();
141                         descriptor.setKey( entry.getKey() );
142                         descriptor.setBlocks( entry.getValue() );
143                         // stream these out in the loop.
144                         oos.writeObject( descriptor );
145                     }
146                 }
147                 finally
148                 {
149                     oos.flush();
150                     oos.close();
151                 }
152             }
153 
154             if ( log.isInfoEnabled() )
155             {
156                 log.info( logCacheName + "Finished saving keys. It took " + timer.getElapsedTimeString() + " to store "
157                     + numKeys + " keys.  Key file length [" + keyFile.length() + "]" );
158             }
159         }
160         catch ( IOException e )
161         {
162             log.error( logCacheName + "Problem storing keys.", e );
163         }
164     }
165 
166     /**
167      * Resets the file and creates a new key map.
168      */
169     protected void reset()
170     {
171         synchronized (keyFile)
172         {
173             clearMemoryMap();
174             saveKeys();
175         }
176 
177     }
178 
179     /**
180      * This is mainly used for testing. It leave the disk in tact, and just clears memory.
181      */
182     protected void clearMemoryMap()
183     {
184         this.keyHash.clear();
185     }
186 
187     /**
188      * Create the map for keys that contain the index position on disk.
189      */
190     private void initKeyMap()
191     {
192         keyHash = null;
193         if ( maxKeySize >= 0 )
194         {
195             keyHash = new LRUMap( maxKeySize );
196             if ( log.isInfoEnabled() )
197             {
198                 log.info( logCacheName + "Set maxKeySize to: '" + maxKeySize + "'" );
199             }
200         }
201         else
202         {
203             // If no max size, use a plain map for memory and processing efficiency.
204             keyHash = new HashMap<K, int[]>();
205             // keyHash = Collections.synchronizedMap( new HashMap() );
206             if ( log.isInfoEnabled() )
207             {
208                 log.info( logCacheName + "Set maxKeySize to unlimited'" );
209             }
210         }
211     }
212 
213     /**
214      * Loads the keys from the .key file. The keys are stored individually on disk. They are added
215      * one by one to an LRUMap..
216      */
217     protected void loadKeys()
218     {
219         if ( log.isInfoEnabled() )
220         {
221             log.info( logCacheName + "Loading keys for " + keyFile.toString() );
222         }
223 
224         try
225         {
226             // create a key map to use.
227             initKeyMap();
228 
229             HashMap<K, int[]> keys = new HashMap<K, int[]>();
230 
231             synchronized (keyFile)
232             {
233                 FileInputStream fis = new FileInputStream( keyFile );
234                 BufferedInputStream bis = new BufferedInputStream( fis );
235                 ObjectInputStream ois = new ObjectInputStream( bis );
236                 try
237                 {
238                     while ( true )
239                     {
240                         @SuppressWarnings("unchecked") // Need to cast from Object
241                         BlockDiskElementDescriptor<K> descriptor = (BlockDiskElementDescriptor<K>) ois.readObject();
242                         if ( descriptor != null )
243                         {
244                             keys.put( descriptor.getKey(), descriptor.getBlocks() );
245                         }
246                     }
247                 }
248                 catch ( EOFException eof )
249                 {
250                     // nothing
251                 }
252                 finally
253                 {
254                     ois.close();
255                 }
256             }
257 
258             if ( !keys.isEmpty() )
259             {
260                 keyHash.putAll( keys );
261 
262                 if ( log.isDebugEnabled() )
263                 {
264                     log.debug( logCacheName + "Found " + keys.size() + " in keys file." );
265                 }
266 
267                 if ( log.isInfoEnabled() )
268                 {
269                     log.info( logCacheName + "Loaded keys from [" + fileName + "], key count: " + keyHash.size()
270                         + "; up to " + maxKeySize + " will be available." );
271                 }
272             }
273         }
274         catch ( Exception e )
275         {
276             log.error( logCacheName + "Problem loading keys for file " + fileName, e );
277         }
278     }
279 
280     /**
281      * Gets the entry set.
282      * <p>
283      * @return entry set.
284      */
285     public Set<Map.Entry<K, int[]>> entrySet()
286     {
287         return this.keyHash.entrySet();
288     }
289 
290     /**
291      * Gets the key set.
292      * <p>
293      * @return key set.
294      */
295     public Set<K> keySet()
296     {
297         return this.keyHash.keySet();
298     }
299 
300     /**
301      * Gets the size of the key hash.
302      * <p>
303      * @return the number of keys.
304      */
305     public int size()
306     {
307         return this.keyHash.size();
308     }
309 
310     /**
311      * gets the object for the key.
312      * <p>
313      * @param key
314      * @return Object
315      */
316     public int[] get( K key )
317     {
318         return this.keyHash.get( key );
319     }
320 
321     /**
322      * Puts a int[] in the keyStore.
323      * <p>
324      * @param key
325      * @param value
326      */
327     public void put( K key, int[] value )
328     {
329         this.keyHash.put( key, value );
330     }
331 
332     /**
333      * Remove by key.
334      * <p>
335      * @param key
336      * @return BlockDiskElementDescriptor if it was present, else null
337      */
338     public int[] remove( K key )
339     {
340         return this.keyHash.remove( key );
341     }
342 
343     /**
344      * Class for recycling and lru. This implements the LRU overflow callback, so we can mark the
345      * blocks as free.
346      */
347     public class LRUMap
348         extends LRUMapJCS<K, int[]>
349     {
350         /** Don't change */
351         private static final long serialVersionUID = 4955079991472142198L;
352 
353         /**
354          * <code>tag</code> tells us which map we are working on.
355          */
356         public String tag = "orig";
357 
358         /**
359          * Default
360          */
361         public LRUMap()
362         {
363             super();
364         }
365 
366         /**
367          * @param maxKeySize
368          */
369         public LRUMap( int maxKeySize )
370         {
371             super( maxKeySize );
372         }
373 
374         /**
375          * This is called when the may key size is reached. The least recently used item will be
376          * passed here. We will store the position and size of the spot on disk in the recycle bin.
377          * <p>
378          * @param key
379          * @param value
380          */
381         @Override
382         protected void processRemovedLRU( K key, int[] value )
383         {
384             blockDiskCache.freeBlocks( value );
385             if ( log.isDebugEnabled() )
386             {
387                 log.debug( logCacheName + "Removing key: [" + key + "] from key store." );
388                 log.debug( logCacheName + "Key store size: [" + super.size() + "]." );
389             }
390         }
391     }
392 }