src/core/net/sf/basedb/util/StaticCache.java

Code
Comments
Other
Rev Date Author Line
4826 20 Mar 09 nicklas 1 /**
4826 20 Mar 09 nicklas 2   $Id$
4826 20 Mar 09 nicklas 3
4826 20 Mar 09 nicklas 4   Copyright (C) 2009 Nicklas Nordborg
4826 20 Mar 09 nicklas 5
4826 20 Mar 09 nicklas 6   This file is part of BASE - BioArray Software Environment.
4826 20 Mar 09 nicklas 7   Available at http://base.thep.lu.se/
4826 20 Mar 09 nicklas 8
4826 20 Mar 09 nicklas 9   BASE is free software; you can redistribute it and/or
4826 20 Mar 09 nicklas 10   modify it under the terms of the GNU General Public License
4826 20 Mar 09 nicklas 11   as published by the Free Software Foundation; either version 3
4826 20 Mar 09 nicklas 12   of the License, or (at your option) any later version.
4826 20 Mar 09 nicklas 13
4826 20 Mar 09 nicklas 14   BASE is distributed in the hope that it will be useful,
4826 20 Mar 09 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
4826 20 Mar 09 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4826 20 Mar 09 nicklas 17   GNU General Public License for more details.
4826 20 Mar 09 nicklas 18
4826 20 Mar 09 nicklas 19   You should have received a copy of the GNU General Public License
4826 20 Mar 09 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
4826 20 Mar 09 nicklas 21 */
4826 20 Mar 09 nicklas 22 package net.sf.basedb.util;
4826 20 Mar 09 nicklas 23
7713 21 May 19 nicklas 24 import java.io.Closeable;
4826 20 Mar 09 nicklas 25 import java.io.File;
4827 23 Mar 09 nicklas 26 import java.io.FileFilter;
4826 20 Mar 09 nicklas 27 import java.io.FileInputStream;
5384 13 Aug 10 nicklas 28 import java.io.FileNotFoundException;
4826 20 Mar 09 nicklas 29 import java.io.FileOutputStream;
4827 23 Mar 09 nicklas 30 import java.io.FilterInputStream;
4827 23 Mar 09 nicklas 31 import java.io.FilterOutputStream;
4826 20 Mar 09 nicklas 32 import java.io.IOException;
4826 20 Mar 09 nicklas 33 import java.io.InputStream;
4826 20 Mar 09 nicklas 34 import java.io.ObjectInputStream;
4826 20 Mar 09 nicklas 35 import java.io.ObjectOutputStream;
7324 06 Apr 17 nicklas 36 import java.io.ObjectStreamClass;
4826 20 Mar 09 nicklas 37 import java.io.OutputStream;
4826 20 Mar 09 nicklas 38 import java.io.Serializable;
4827 23 Mar 09 nicklas 39 import java.lang.ref.WeakReference;
7713 21 May 19 nicklas 40 import java.lang.ref.Cleaner.Cleanable;
4827 23 Mar 09 nicklas 41 import java.util.List;
4827 23 Mar 09 nicklas 42 import java.util.Map;
4827 23 Mar 09 nicklas 43 import java.util.TimerTask;
4827 23 Mar 09 nicklas 44 import java.util.WeakHashMap;
4827 23 Mar 09 nicklas 45 import java.util.concurrent.TimeUnit;
4827 23 Mar 09 nicklas 46 import java.util.concurrent.locks.Lock;
4827 23 Mar 09 nicklas 47 import java.util.concurrent.locks.ReadWriteLock;
4827 23 Mar 09 nicklas 48 import java.util.concurrent.locks.ReentrantReadWriteLock;
4827 23 Mar 09 nicklas 49 import java.util.regex.Pattern;
4826 20 Mar 09 nicklas 50
7715 22 May 19 nicklas 51 import net.sf.basedb.core.Application;
7141 22 Apr 16 nicklas 52 import net.sf.basedb.core.BaseException;
4827 23 Mar 09 nicklas 53 import net.sf.basedb.core.InvalidDataException;
4827 23 Mar 09 nicklas 54
4826 20 Mar 09 nicklas 55 /**
4826 20 Mar 09 nicklas 56   A cache for storing data to files on the file system. The typical
4826 20 Mar 09 nicklas 57   use case is to store data this is expensive to get from the database
4826 20 Mar 09 nicklas 58   in a file for later retrieval. The cache can be used in streaming mode
4827 23 Mar 09 nicklas 59   with the any of the {@link #read(String, int)} or {@link #write(String, InputStream, int)}
4826 20 Mar 09 nicklas 60   methods and their variants. It can also be used to store any {@link Serializable}
4829 23 Mar 09 nicklas 61   object with {@link #store(String, Object, int)} and {@link #load(String, int)}.
4826 20 Mar 09 nicklas 62   <p>
4827 23 Mar 09 nicklas 63   
4826 20 Mar 09 nicklas 64   In all cases the cached entry is identified by a key which is more or
4826 20 Mar 09 nicklas 65   less directly translated to directories on the file system.
4826 20 Mar 09 nicklas 66   <p>
4826 20 Mar 09 nicklas 67   
4827 23 Mar 09 nicklas 68   This class is thread-safe and can be used by multiple threads at the same
4827 23 Mar 09 nicklas 69   time. Write requests to the same entry in the cache are allowed to one 
4827 23 Mar 09 nicklas 70   thread at a time. Any number of threads may read from the same cache entry
4827 23 Mar 09 nicklas 71   as long as no thread is writing to that entry.
4826 20 Mar 09 nicklas 72   
4826 20 Mar 09 nicklas 73   @author Nicklas
4826 20 Mar 09 nicklas 74   @version 2.11
4826 20 Mar 09 nicklas 75   @base.modified $Date$
4826 20 Mar 09 nicklas 76 */
4826 20 Mar 09 nicklas 77 public class StaticCache
4826 20 Mar 09 nicklas 78 {
4827 23 Mar 09 nicklas 79
4827 23 Mar 09 nicklas 80   public static final Pattern validKey = Pattern.compile("[\\w\\/\\.\\-]+");
4827 23 Mar 09 nicklas 81
4827 23 Mar 09 nicklas 82   /**
4827 23 Mar 09 nicklas 83     Log static cache events.
4827 23 Mar 09 nicklas 84   */
6444 09 Apr 14 nicklas 85   private static final org.slf4j.Logger log = 
6444 09 Apr 14 nicklas 86     org.slf4j.LoggerFactory.getLogger(StaticCache.class);
4827 23 Mar 09 nicklas 87
4827 23 Mar 09 nicklas 88   /**
4827 23 Mar 09 nicklas 89     Checks if the given key is a vilid cache entry key.
4827 23 Mar 09 nicklas 90     Allowed characters are: any alphanumerical character + ./-
4827 23 Mar 09 nicklas 91     @param key The key to check
4827 23 Mar 09 nicklas 92     @return TRUE if the key is valid
4827 23 Mar 09 nicklas 93   */
4827 23 Mar 09 nicklas 94   public static boolean isValidKey(String key)
4827 23 Mar 09 nicklas 95   {
7141 22 Apr 16 nicklas 96     return validKey.matcher(key).matches() && !key.contains("../");
4827 23 Mar 09 nicklas 97   }
4827 23 Mar 09 nicklas 98   
4976 18 Jun 09 nicklas 99   /**
4976 18 Jun 09 nicklas 100     Convert a possible invalid cache key to a valid one by 
4976 18 Jun 09 nicklas 101     replacing invalid characters with the replacement string.
4976 18 Jun 09 nicklas 102     @param key The (possible invalid) cache key
4976 18 Jun 09 nicklas 103     @param replacement The replacement string
4976 18 Jun 09 nicklas 104     @return A valid cache key
4976 18 Jun 09 nicklas 105     @since 2.13
4976 18 Jun 09 nicklas 106   */
4976 18 Jun 09 nicklas 107   public static String makeValidKey(String key, String replacement)
4976 18 Jun 09 nicklas 108   {
4976 18 Jun 09 nicklas 109     Pattern invalid = Pattern.compile("[^\\w\\/\\.\\-]");
7141 22 Apr 16 nicklas 110     return invalid.matcher(key).replaceAll(replacement).replace("../", replacement);
4976 18 Jun 09 nicklas 111   }
4976 18 Jun 09 nicklas 112   
4826 20 Mar 09 nicklas 113   private final File root;
7141 22 Apr 16 nicklas 114   private final String rootPath;
4827 23 Mar 09 nicklas 115   private final Map<String, LockEntry> locks;
4826 20 Mar 09 nicklas 116   private boolean disabled;
4826 20 Mar 09 nicklas 117   
4827 23 Mar 09 nicklas 118   
4826 20 Mar 09 nicklas 119   /**
4826 20 Mar 09 nicklas 120     Creates a new static cache.
4826 20 Mar 09 nicklas 121     @param root A directory were the cache stores it's files
4826 20 Mar 09 nicklas 122   */
4826 20 Mar 09 nicklas 123   public StaticCache(File root)
4826 20 Mar 09 nicklas 124   {
4826 20 Mar 09 nicklas 125     this.root = root;
7141 22 Apr 16 nicklas 126     try
7141 22 Apr 16 nicklas 127     {
7141 22 Apr 16 nicklas 128       this.rootPath = root.getCanonicalPath();
7141 22 Apr 16 nicklas 129     }
7141 22 Apr 16 nicklas 130     catch (IOException ex)
7141 22 Apr 16 nicklas 131     {
7141 22 Apr 16 nicklas 132       throw new BaseException(ex);
7141 22 Apr 16 nicklas 133     }
4827 23 Mar 09 nicklas 134     this.locks = new WeakHashMap<String, LockEntry>();
4827 23 Mar 09 nicklas 135     log.info("Creating static cache in directory " + root);
4826 20 Mar 09 nicklas 136   }
4826 20 Mar 09 nicklas 137
4826 20 Mar 09 nicklas 138   /**
4826 20 Mar 09 nicklas 139     Check if the cache has been disabled. A disabled cache ignores
4826 20 Mar 09 nicklas 140     all calls to read or write data.
4826 20 Mar 09 nicklas 141     @return A boolean that is TRUE if the cache is disabled
4826 20 Mar 09 nicklas 142   */
4826 20 Mar 09 nicklas 143   public boolean isDisabled()
4826 20 Mar 09 nicklas 144   {
4826 20 Mar 09 nicklas 145     return disabled;
4826 20 Mar 09 nicklas 146   }
4826 20 Mar 09 nicklas 147   
4826 20 Mar 09 nicklas 148   /**
4826 20 Mar 09 nicklas 149     Disable or enable the cache.
4826 20 Mar 09 nicklas 150     @param disabled TRUE if the cache should be disabled
4826 20 Mar 09 nicklas 151   */
4826 20 Mar 09 nicklas 152   public void setDisabled(boolean disabled)
4826 20 Mar 09 nicklas 153   {
4827 23 Mar 09 nicklas 154     if (disabled)
4827 23 Mar 09 nicklas 155     {
4827 23 Mar 09 nicklas 156       log.info("Disabling static cache in directory " + root);
4827 23 Mar 09 nicklas 157     }
4827 23 Mar 09 nicklas 158     else
4827 23 Mar 09 nicklas 159     {
4827 23 Mar 09 nicklas 160       log.info("Enabling static cache in directory " + root);
4827 23 Mar 09 nicklas 161     }
4826 20 Mar 09 nicklas 162     this.disabled = disabled;
4826 20 Mar 09 nicklas 163   }
4826 20 Mar 09 nicklas 164   
4826 20 Mar 09 nicklas 165   /**
4827 23 Mar 09 nicklas 166     Remove all files that matches the specified filter from
4827 23 Mar 09 nicklas 167     the cache. This method is synchronized and can only
4827 23 Mar 09 nicklas 168     be executed by one thread at a time.
4827 23 Mar 09 nicklas 169     
4827 23 Mar 09 nicklas 170     @param filter A file filter that matches the files to
4827 23 Mar 09 nicklas 171       remove
4827 23 Mar 09 nicklas 172   */
4827 23 Mar 09 nicklas 173   public synchronized void cleanUp(FileFilter filter)
4827 23 Mar 09 nicklas 174   {
4827 23 Mar 09 nicklas 175     log.info("Cleaning up static cache: " + root);
4827 23 Mar 09 nicklas 176     List<File> oldFiles = FileUtil.findFiles(root, filter);
4827 23 Mar 09 nicklas 177     if (oldFiles == null) return;
4827 23 Mar 09 nicklas 178     
4827 23 Mar 09 nicklas 179     log.debug("Found " + oldFiles.size() + " files that should be deleted");
4827 23 Mar 09 nicklas 180     int numDeleted = 0;
4827 23 Mar 09 nicklas 181     for (File f : oldFiles)
4827 23 Mar 09 nicklas 182     {
4827 23 Mar 09 nicklas 183       if (f.delete())
4827 23 Mar 09 nicklas 184       {
4827 23 Mar 09 nicklas 185         log.debug("Removed cached file: " + f);
4827 23 Mar 09 nicklas 186         numDeleted++;
4827 23 Mar 09 nicklas 187       }
4827 23 Mar 09 nicklas 188       else
4827 23 Mar 09 nicklas 189       {
4827 23 Mar 09 nicklas 190         log.warn("Failed to remove cached file: " + f);
4827 23 Mar 09 nicklas 191       }
4827 23 Mar 09 nicklas 192     }
4977 23 Jun 09 nicklas 193     int numDirectories = FileUtil.deleteEmptyDirectories(root, false);
4977 23 Jun 09 nicklas 194     log.info("Removed " + numDeleted + " files and " + numDirectories + 
4977 23 Jun 09 nicklas 195         " directories from the static cache: " + root);
4827 23 Mar 09 nicklas 196   }
4827 23 Mar 09 nicklas 197   
4827 23 Mar 09 nicklas 198   /**
4827 23 Mar 09 nicklas 199     Creates a file filter that matches all files that
4827 23 Mar 09 nicklas 200     are older than the specified age.
4827 23 Mar 09 nicklas 201     @param age The age in milliseconds
4827 23 Mar 09 nicklas 202     @return A file filter
4827 23 Mar 09 nicklas 203     @see OlderThanFileFilter
4827 23 Mar 09 nicklas 204   */
4827 23 Mar 09 nicklas 205   public FileFilter olderThan(long age)
4827 23 Mar 09 nicklas 206   {
4827 23 Mar 09 nicklas 207     return new OlderThanFileFilter(age, true);
4827 23 Mar 09 nicklas 208   }
4827 23 Mar 09 nicklas 209   
4827 23 Mar 09 nicklas 210   /**
4827 23 Mar 09 nicklas 211     Creates a task that cleans up this cache when it is 
4827 23 Mar 09 nicklas 212     executed.
4827 23 Mar 09 nicklas 213     @param filter The file filter that determines
4827 23 Mar 09 nicklas 214       which file that should be deleted
4827 23 Mar 09 nicklas 215     @return A TimerTask object
4827 23 Mar 09 nicklas 216     @see #cleanUp(FileFilter)
4827 23 Mar 09 nicklas 217   */
4827 23 Mar 09 nicklas 218   public TimerTask cleanUpTask(FileFilter filter)
4827 23 Mar 09 nicklas 219   {
4827 23 Mar 09 nicklas 220     return new CleanupTask(this, filter);
4827 23 Mar 09 nicklas 221   }
4827 23 Mar 09 nicklas 222   
4827 23 Mar 09 nicklas 223   /**
4976 18 Jun 09 nicklas 224     Checks if a cache entry exists or not.
4976 18 Jun 09 nicklas 225     @param key The cache key
4976 18 Jun 09 nicklas 226     @return TRUE if an extry exists, FALSE otherwise
4976 18 Jun 09 nicklas 227     @since 2.13
4976 18 Jun 09 nicklas 228   */
4976 18 Jun 09 nicklas 229   public boolean exists(String key)
4976 18 Jun 09 nicklas 230   {
4976 18 Jun 09 nicklas 231     validateKey(key);
7141 22 Apr 16 nicklas 232     File f = fileFromKey(key);
4976 18 Jun 09 nicklas 233     return f.exists();
4976 18 Jun 09 nicklas 234   }
4976 18 Jun 09 nicklas 235   
4976 18 Jun 09 nicklas 236   /**
4976 18 Jun 09 nicklas 237     Checks the size (in bytes) of a cache entry.
4976 18 Jun 09 nicklas 238     @param key The cache key
4976 18 Jun 09 nicklas 239     @return The size in bytes, or -1 if the cache entry doesn't exists
4976 18 Jun 09 nicklas 240     @since 2.13
4976 18 Jun 09 nicklas 241   */
4976 18 Jun 09 nicklas 242   public long size(String key)
4976 18 Jun 09 nicklas 243   {
4976 18 Jun 09 nicklas 244     validateKey(key);
7141 22 Apr 16 nicklas 245     File f = fileFromKey(key);
4976 18 Jun 09 nicklas 246     return f.exists() ? f.length() : -1;
4976 18 Jun 09 nicklas 247   }
4976 18 Jun 09 nicklas 248   
4976 18 Jun 09 nicklas 249   /**
4827 23 Mar 09 nicklas 250     Store binary information in the cache. If the entry already exists,
4826 20 Mar 09 nicklas 251     it is overwritten. If the entry is locked by other threads,
4827 23 Mar 09 nicklas 252     this thread waits the specified number of milliseconds before
4827 23 Mar 09 nicklas 253     returning.
4826 20 Mar 09 nicklas 254     
4826 20 Mar 09 nicklas 255     @param key The cache key
4826 20 Mar 09 nicklas 256     @param in An input stream to read from
4827 23 Mar 09 nicklas 257     @param timeout A timeout in milliseconds to wait for a write lock
4827 23 Mar 09 nicklas 258       on the requested cache entry
4826 20 Mar 09 nicklas 259     @return The number of bytes written
4826 20 Mar 09 nicklas 260   */
4827 23 Mar 09 nicklas 261   public long write(String key, InputStream in, int timeout)
4826 20 Mar 09 nicklas 262     throws IOException
4826 20 Mar 09 nicklas 263   {
4826 20 Mar 09 nicklas 264     if (disabled) return 0;
4827 23 Mar 09 nicklas 265     long numBytes = 0;
4827 23 Mar 09 nicklas 266     OutputStream out = null;
4827 23 Mar 09 nicklas 267     try
4827 23 Mar 09 nicklas 268     {
4827 23 Mar 09 nicklas 269       out = getOutputStream(key, timeout);
4827 23 Mar 09 nicklas 270       if (out != null)
4827 23 Mar 09 nicklas 271       {
4827 23 Mar 09 nicklas 272         numBytes = FileUtil.copy(in, out);
4827 23 Mar 09 nicklas 273         out.flush();
4827 23 Mar 09 nicklas 274       }
4827 23 Mar 09 nicklas 275     }
4827 23 Mar 09 nicklas 276     finally
4827 23 Mar 09 nicklas 277     {
7713 21 May 19 nicklas 278       FileUtil.close(out);
4827 23 Mar 09 nicklas 279     }
4826 20 Mar 09 nicklas 280     return numBytes;
4826 20 Mar 09 nicklas 281   }
4826 20 Mar 09 nicklas 282   
4827 23 Mar 09 nicklas 283   /**
4827 23 Mar 09 nicklas 284     Get an output stream that can be used to write binary information
4827 23 Mar 09 nicklas 285     to the cache. If the entry already exists, it is overwritten. 
4827 23 Mar 09 nicklas 286     If the entry is locked by other threads, this thread waits the 
4827 23 Mar 09 nicklas 287     specified number of milliseconds before giving up.
4827 23 Mar 09 nicklas 288     <p>
4827 23 Mar 09 nicklas 289     NOTE! It is very important that the caller closes the 
4827 23 Mar 09 nicklas 290     output stream as soon as all data has been written to it.
4827 23 Mar 09 nicklas 291     Failure to do so may result in locking the cache entry from
4827 23 Mar 09 nicklas 292     reading by other threads.
4827 23 Mar 09 nicklas 293     
4827 23 Mar 09 nicklas 294     @param key The cache key
4827 23 Mar 09 nicklas 295     @param timeout A timeout in milliseconds to wait for a write lock
4827 23 Mar 09 nicklas 296       on the requested cache entry
4827 23 Mar 09 nicklas 297     @return An output stream, or null if a write lock could not
4827 23 Mar 09 nicklas 298       be aquired
4827 23 Mar 09 nicklas 299   */
4827 23 Mar 09 nicklas 300   public OutputStream write(String key, int timeout)
4826 20 Mar 09 nicklas 301     throws IOException
4826 20 Mar 09 nicklas 302   {
4826 20 Mar 09 nicklas 303     if (disabled) return null;
4827 23 Mar 09 nicklas 304     return getOutputStream(key, timeout);
4826 20 Mar 09 nicklas 305   }
4826 20 Mar 09 nicklas 306   
4827 23 Mar 09 nicklas 307   /**
4827 23 Mar 09 nicklas 308     Read binary information from the cache. The contents of the 
4827 23 Mar 09 nicklas 309     specified cache entry will be copied to the specified output 
4827 23 Mar 09 nicklas 310     stream.
4827 23 Mar 09 nicklas 311
4827 23 Mar 09 nicklas 312     @param key The cache key
4827 23 Mar 09 nicklas 313     @param out An output stream to write the cache contents to
4827 23 Mar 09 nicklas 314     @param timeout A timeout in milliseconds to wait for a 
4827 23 Mar 09 nicklas 315       read lock on the requested cache entry
4827 23 Mar 09 nicklas 316     @return The number of bytes copied
4827 23 Mar 09 nicklas 317   */
4827 23 Mar 09 nicklas 318   public long read(String key, OutputStream out, int timeout)
4826 20 Mar 09 nicklas 319     throws IOException
4826 20 Mar 09 nicklas 320   {
4826 20 Mar 09 nicklas 321     if (disabled) return 0;
4826 20 Mar 09 nicklas 322     long numBytes = 0;
4827 23 Mar 09 nicklas 323     InputStream in = null;
4827 23 Mar 09 nicklas 324     try
4827 23 Mar 09 nicklas 325     {
4827 23 Mar 09 nicklas 326       in = getInputStream(key, timeout);
4827 23 Mar 09 nicklas 327       if (in != null) 
4827 23 Mar 09 nicklas 328       {
4827 23 Mar 09 nicklas 329         numBytes = FileUtil.copy(in, out);
4827 23 Mar 09 nicklas 330       }
4827 23 Mar 09 nicklas 331     }
4827 23 Mar 09 nicklas 332     finally
4827 23 Mar 09 nicklas 333     {
7713 21 May 19 nicklas 334       FileUtil.close(in);
4827 23 Mar 09 nicklas 335     }
4826 20 Mar 09 nicklas 336     return numBytes;
4826 20 Mar 09 nicklas 337   }
4826 20 Mar 09 nicklas 338   
4827 23 Mar 09 nicklas 339   /**
4827 23 Mar 09 nicklas 340     Get an input stream for reading binary information from the cache.
4827 23 Mar 09 nicklas 341   
4827 23 Mar 09 nicklas 342     @param key The cache key
4827 23 Mar 09 nicklas 343     @param timeout A timeout in milliseconds to wait for a 
4827 23 Mar 09 nicklas 344       read lock on the requested cache entry
4827 23 Mar 09 nicklas 345     @return An input stream or null if the cache entry doesn't exists
4827 23 Mar 09 nicklas 346       or if a read lock could not be aquired
4827 23 Mar 09 nicklas 347   */
4827 23 Mar 09 nicklas 348   public InputStream read(String key, int timeout)
4826 20 Mar 09 nicklas 349     throws IOException
4826 20 Mar 09 nicklas 350   {
4826 20 Mar 09 nicklas 351     if (disabled) return null;
4827 23 Mar 09 nicklas 352     return getInputStream(key, timeout);
4826 20 Mar 09 nicklas 353   }
4826 20 Mar 09 nicklas 354   
4827 23 Mar 09 nicklas 355   /**
4827 23 Mar 09 nicklas 356     Store a serializable object in the cache. If the entry already exists,
4827 23 Mar 09 nicklas 357     it is overwritten. If the entry is locked by other threads,
4827 23 Mar 09 nicklas 358     this thread waits the specified number of milliseconds before
4827 23 Mar 09 nicklas 359     returning.
4827 23 Mar 09 nicklas 360     
4827 23 Mar 09 nicklas 361     @param key The cache key
4829 23 Mar 09 nicklas 362     @param object The object to store in the cache, it must be a
4829 23 Mar 09 nicklas 363       {@link Serializable} object
4827 23 Mar 09 nicklas 364     @param timeout A timeout in milliseconds to wait for a write lock
4827 23 Mar 09 nicklas 365       on the requested cache entry
4827 23 Mar 09 nicklas 366     @return TRUE if the object could be stored, FALSE otherwise
4827 23 Mar 09 nicklas 367   */
4829 23 Mar 09 nicklas 368   public boolean store(String key, Object object, int timeout)
4826 20 Mar 09 nicklas 369   {
4826 20 Mar 09 nicklas 370     if (disabled) return false;
4827 23 Mar 09 nicklas 371     OutputStream out = null;
4829 23 Mar 09 nicklas 372     boolean stored = false;
4827 23 Mar 09 nicklas 373     try
4827 23 Mar 09 nicklas 374     {
4827 23 Mar 09 nicklas 375       out = getOutputStream(key, timeout);
4827 23 Mar 09 nicklas 376       if (out != null)
4827 23 Mar 09 nicklas 377       {
4827 23 Mar 09 nicklas 378         ObjectOutputStream oos = new ObjectOutputStream(out);
4827 23 Mar 09 nicklas 379         oos.writeObject(object);
4827 23 Mar 09 nicklas 380         oos.flush();
4829 23 Mar 09 nicklas 381         stored = true;
4827 23 Mar 09 nicklas 382       }
4827 23 Mar 09 nicklas 383     }
4829 23 Mar 09 nicklas 384     catch (IOException ex)
4829 23 Mar 09 nicklas 385     {
4829 23 Mar 09 nicklas 386       log.warn("Could not store cached entry: " + key, ex);
4829 23 Mar 09 nicklas 387     }
4827 23 Mar 09 nicklas 388     finally
4827 23 Mar 09 nicklas 389     {
7713 21 May 19 nicklas 390       FileUtil.close(out);
4827 23 Mar 09 nicklas 391     }
4829 23 Mar 09 nicklas 392     return stored;
4826 20 Mar 09 nicklas 393   }
4826 20 Mar 09 nicklas 394   
4827 23 Mar 09 nicklas 395   /**
4827 23 Mar 09 nicklas 396     Read a serializable object from the cache.
4827 23 Mar 09 nicklas 397     
4827 23 Mar 09 nicklas 398     @param key The cache key
4827 23 Mar 09 nicklas 399     @param timeout A timeout in milliseconds to wait for a read lock
4827 23 Mar 09 nicklas 400       on the requested cache entry
4827 23 Mar 09 nicklas 401     @return The materialized object, or null if the cache entry doesn't
4827 23 Mar 09 nicklas 402       exists or if a read lock couldn't be aquired
4827 23 Mar 09 nicklas 403   */
7610 27 Feb 19 nicklas 404   public <T> T load(String key, int timeout)
4826 20 Mar 09 nicklas 405   {
7324 06 Apr 17 nicklas 406     return load(key, null, timeout);
7324 06 Apr 17 nicklas 407   }
7324 06 Apr 17 nicklas 408   
7610 27 Feb 19 nicklas 409   @SuppressWarnings("unchecked")
7610 27 Feb 19 nicklas 410   public <T> T load(String key, ClassLoader loader, int timeout)
7324 06 Apr 17 nicklas 411   {
4826 20 Mar 09 nicklas 412     if (disabled) return null;
4826 20 Mar 09 nicklas 413     Serializable object = null;
4827 23 Mar 09 nicklas 414     InputStream in = null;
4827 23 Mar 09 nicklas 415     try
4826 20 Mar 09 nicklas 416     {
4827 23 Mar 09 nicklas 417       in = getInputStream(key, timeout);
4827 23 Mar 09 nicklas 418       if (in != null)
4826 20 Mar 09 nicklas 419       {
7324 06 Apr 17 nicklas 420         // Create stream and override resolveClass so we
7324 06 Apr 17 nicklas 421         // can load classes from the given class loader
7324 06 Apr 17 nicklas 422         ObjectInputStream oin = new ObjectInputStream(in)
7324 06 Apr 17 nicklas 423         {
7324 06 Apr 17 nicklas 424           @Override
7324 06 Apr 17 nicklas 425           protected Class<?> resolveClass(ObjectStreamClass streamClass)
7324 06 Apr 17 nicklas 426               throws IOException, ClassNotFoundException
7324 06 Apr 17 nicklas 427           {
7324 06 Apr 17 nicklas 428             Class<?> c = null;
7324 06 Apr 17 nicklas 429             try
7324 06 Apr 17 nicklas 430             {
7324 06 Apr 17 nicklas 431               c = super.resolveClass(streamClass);
7324 06 Apr 17 nicklas 432             }
7324 06 Apr 17 nicklas 433             catch (ClassNotFoundException ex)
7324 06 Apr 17 nicklas 434             {
7324 06 Apr 17 nicklas 435               if (loader == null) throw ex;
7324 06 Apr 17 nicklas 436               c = Class.forName(streamClass.getName(), false, loader);
7324 06 Apr 17 nicklas 437             }
7324 06 Apr 17 nicklas 438             return c;
7324 06 Apr 17 nicklas 439           }
7324 06 Apr 17 nicklas 440         };
4827 23 Mar 09 nicklas 441         try
4827 23 Mar 09 nicklas 442         {
4827 23 Mar 09 nicklas 443           object = (Serializable)oin.readObject();
4827 23 Mar 09 nicklas 444         }
4827 23 Mar 09 nicklas 445         catch (ClassNotFoundException ex)
4827 23 Mar 09 nicklas 446         {
4827 23 Mar 09 nicklas 447           throw new IOException(ex);
4827 23 Mar 09 nicklas 448         }
4826 20 Mar 09 nicklas 449       }
4826 20 Mar 09 nicklas 450     }
4829 23 Mar 09 nicklas 451     catch (IOException ex)
4829 23 Mar 09 nicklas 452     {
4829 23 Mar 09 nicklas 453       log.warn("Could not load cached entry: " + key, ex);
4829 23 Mar 09 nicklas 454     }
4827 23 Mar 09 nicklas 455     finally
4827 23 Mar 09 nicklas 456     {
7713 21 May 19 nicklas 457       FileUtil.close(in);
4827 23 Mar 09 nicklas 458     }
7610 27 Feb 19 nicklas 459     return (T)object;    
7324 06 Apr 17 nicklas 460     
4826 20 Mar 09 nicklas 461   }
4826 20 Mar 09 nicklas 462   
5121 09 Oct 09 nicklas 463   /**
5121 09 Oct 09 nicklas 464     Deletes an object from the cache.
5121 09 Oct 09 nicklas 465     
5121 09 Oct 09 nicklas 466     @param key The cache key
5121 09 Oct 09 nicklas 467     @param timeout A timeout in milliseconds to wait for a write lock
5121 09 Oct 09 nicklas 468       on the requested cache entry
5121 09 Oct 09 nicklas 469     @return TRUE if the cached object was deleted or if it didn't exist to
5121 09 Oct 09 nicklas 470       begin with or if the static cache is disabled, FALSE is only returned
5121 09 Oct 09 nicklas 471       if it is certain that the cached object still exists after this call
5121 09 Oct 09 nicklas 472     @since 2.14
5121 09 Oct 09 nicklas 473   */
5121 09 Oct 09 nicklas 474   public boolean delete(String key, int timeout)
5121 09 Oct 09 nicklas 475   {
5121 09 Oct 09 nicklas 476     if (disabled) return true;
5121 09 Oct 09 nicklas 477     validateKey(key);
5121 09 Oct 09 nicklas 478     log.debug("Delete request for static cache: " + key);
7141 22 Apr 16 nicklas 479     File f = fileFromKey(key);
5121 09 Oct 09 nicklas 480     if (!f.exists()) 
5121 09 Oct 09 nicklas 481     {
5121 09 Oct 09 nicklas 482       log.debug("Cache entry doesn't exist: " + key);
5121 09 Oct 09 nicklas 483       return true;
5121 09 Oct 09 nicklas 484     }
5121 09 Oct 09 nicklas 485     LockEntry lock = aquireLock(key, true, timeout);
5121 09 Oct 09 nicklas 486     if (lock == null) return false;
5121 09 Oct 09 nicklas 487     try
5121 09 Oct 09 nicklas 488     {
5121 09 Oct 09 nicklas 489       return f.delete();
5121 09 Oct 09 nicklas 490     }
5121 09 Oct 09 nicklas 491     finally
5121 09 Oct 09 nicklas 492     {
5121 09 Oct 09 nicklas 493       lock.writeLock().unlock();
5121 09 Oct 09 nicklas 494     }
5121 09 Oct 09 nicklas 495   }
5121 09 Oct 09 nicklas 496   
4826 20 Mar 09 nicklas 497   private void validateKey(String key)
4826 20 Mar 09 nicklas 498   {
4827 23 Mar 09 nicklas 499     if (!isValidKey(key))
4827 23 Mar 09 nicklas 500     {
4827 23 Mar 09 nicklas 501       throw new InvalidDataException("Invalid cache key: " + key);
4827 23 Mar 09 nicklas 502     }
4826 20 Mar 09 nicklas 503   }
4826 20 Mar 09 nicklas 504   
7141 22 Apr 16 nicklas 505   private File fileFromKey(String key)
7141 22 Apr 16 nicklas 506   {
7141 22 Apr 16 nicklas 507     File f = new File(root, key);
7141 22 Apr 16 nicklas 508     // The key must result in a file path that is a child to the root path!
7141 22 Apr 16 nicklas 509     try
7141 22 Apr 16 nicklas 510     {
7141 22 Apr 16 nicklas 511       String keyPath = f.getCanonicalPath();
7141 22 Apr 16 nicklas 512       if (!keyPath.startsWith(rootPath))
7141 22 Apr 16 nicklas 513       {
7141 22 Apr 16 nicklas 514         throw new InvalidDataException("Invalid path to cache entry: "+ keyPath);
7141 22 Apr 16 nicklas 515       }
7141 22 Apr 16 nicklas 516     }
7141 22 Apr 16 nicklas 517     catch (IOException ex)
7141 22 Apr 16 nicklas 518     {
7141 22 Apr 16 nicklas 519       throw new InvalidDataException("Invalid cache key: "+ key, ex);
7141 22 Apr 16 nicklas 520     }
7141 22 Apr 16 nicklas 521     return f;
7141 22 Apr 16 nicklas 522   }
7141 22 Apr 16 nicklas 523   
4827 23 Mar 09 nicklas 524   /**
4827 23 Mar 09 nicklas 525     Get a lock-safe input stream.
4827 23 Mar 09 nicklas 526   */
4827 23 Mar 09 nicklas 527   private InputStream getInputStream(String key, int timeout)
4826 20 Mar 09 nicklas 528     throws IOException
4826 20 Mar 09 nicklas 529   {
4826 20 Mar 09 nicklas 530     validateKey(key);
4827 23 Mar 09 nicklas 531     log.debug("Read request for static cache: " + key);
7141 22 Apr 16 nicklas 532     File f = fileFromKey(key);
4827 23 Mar 09 nicklas 533     if (!f.exists()) 
4827 23 Mar 09 nicklas 534     {
4827 23 Mar 09 nicklas 535       log.debug("Cache entry doesn't exist: " + key);
4827 23 Mar 09 nicklas 536       return null;
4827 23 Mar 09 nicklas 537     }
4827 23 Mar 09 nicklas 538     LockEntry lock = aquireLock(key, false, timeout);
4827 23 Mar 09 nicklas 539     if (lock == null) return null;
4827 23 Mar 09 nicklas 540     InputStream in = null;
4827 23 Mar 09 nicklas 541     try
4827 23 Mar 09 nicklas 542     {
5384 13 Aug 10 nicklas 543       if (!f.setLastModified(System.currentTimeMillis()))
5384 13 Aug 10 nicklas 544       {
5384 13 Aug 10 nicklas 545         log.warn("Failed to set last modification time on file: " + f);
5384 13 Aug 10 nicklas 546       }
4827 23 Mar 09 nicklas 547       in = new LockSafeInputStream(new FileInputStream(f), lock);
4827 23 Mar 09 nicklas 548     }
4827 23 Mar 09 nicklas 549     finally
4827 23 Mar 09 nicklas 550     {
4827 23 Mar 09 nicklas 551       if (in == null) lock.readLock().unlock();
4827 23 Mar 09 nicklas 552     }
4827 23 Mar 09 nicklas 553     return in;
4826 20 Mar 09 nicklas 554   }
4826 20 Mar 09 nicklas 555
4827 23 Mar 09 nicklas 556   /**
4827 23 Mar 09 nicklas 557     Get a lock-safe output stream.
4827 23 Mar 09 nicklas 558   */
4827 23 Mar 09 nicklas 559   private OutputStream getOutputStream(String key, int timeout)
4826 20 Mar 09 nicklas 560     throws IOException
4826 20 Mar 09 nicklas 561   {
4826 20 Mar 09 nicklas 562     validateKey(key);
4827 23 Mar 09 nicklas 563     log.debug("Write request for static cache: " + key);
4827 23 Mar 09 nicklas 564     LockEntry lock = aquireLock(key, true, timeout);
4827 23 Mar 09 nicklas 565     if (lock == null) return null;
4827 23 Mar 09 nicklas 566     OutputStream out = null;
4827 23 Mar 09 nicklas 567     try
4827 23 Mar 09 nicklas 568     {
7141 22 Apr 16 nicklas 569       File f = fileFromKey(key);
5384 13 Aug 10 nicklas 570       File dir = f.getParentFile();
5384 13 Aug 10 nicklas 571       if (!dir.mkdirs() && !dir.isDirectory())
5384 13 Aug 10 nicklas 572       {
5384 13 Aug 10 nicklas 573         throw new FileNotFoundException("Could not create directory: " + f.getParentFile());
5384 13 Aug 10 nicklas 574       }
5384 13 Aug 10 nicklas 575       if (!f.createNewFile() && !f.exists())
5384 13 Aug 10 nicklas 576       {
5384 13 Aug 10 nicklas 577         throw new FileNotFoundException("Could not create file: " + f);
5384 13 Aug 10 nicklas 578       }
4827 23 Mar 09 nicklas 579       out = new LockSafeOutputStream(new FileOutputStream(f), lock);
4827 23 Mar 09 nicklas 580     }
4827 23 Mar 09 nicklas 581     finally
4827 23 Mar 09 nicklas 582     {
4827 23 Mar 09 nicklas 583       if (out == null) lock.writeLock().unlock();
4827 23 Mar 09 nicklas 584     }
4827 23 Mar 09 nicklas 585     return out;
4826 20 Mar 09 nicklas 586   }
4826 20 Mar 09 nicklas 587
4827 23 Mar 09 nicklas 588   /**
4827 23 Mar 09 nicklas 589     Aquire a read or write lock on a given cache entry.
4827 23 Mar 09 nicklas 590     @return A lock manager or null if the lock could not be aquired
4827 23 Mar 09 nicklas 591   */
4827 23 Mar 09 nicklas 592   private LockEntry aquireLock(String key, boolean writeLock, int timeout)
4827 23 Mar 09 nicklas 593   {
4827 23 Mar 09 nicklas 594     String lockType = writeLock ? "write" : "read";
4827 23 Mar 09 nicklas 595     log.debug("Trying to get " + lockType + " lock on: " + key);
4827 23 Mar 09 nicklas 596     LockEntry rwLock = null;
4827 23 Mar 09 nicklas 597     synchronized (locks)
4827 23 Mar 09 nicklas 598     {
4827 23 Mar 09 nicklas 599       log.debug("Number of known lock entries: " + locks.size());
4827 23 Mar 09 nicklas 600       rwLock = locks.get(key);
4827 23 Mar 09 nicklas 601       if (rwLock == null)
4827 23 Mar 09 nicklas 602       {
4827 23 Mar 09 nicklas 603         log.debug("Create new lock holder for: " + key);
4827 23 Mar 09 nicklas 604         rwLock = new LockEntry(new ReentrantReadWriteLock(), key);
4827 23 Mar 09 nicklas 605         locks.put(key, rwLock);
4827 23 Mar 09 nicklas 606       }
4827 23 Mar 09 nicklas 607     }
4827 23 Mar 09 nicklas 608     
4827 23 Mar 09 nicklas 609     Lock lock = writeLock ? rwLock.writeLock() : rwLock.readLock();
4827 23 Mar 09 nicklas 610     try
4827 23 Mar 09 nicklas 611     {
4827 23 Mar 09 nicklas 612       if (!lock.tryLock(timeout, TimeUnit.MILLISECONDS)) 
4827 23 Mar 09 nicklas 613       {
4827 23 Mar 09 nicklas 614         log.debug("Timeout while waiting for " + lockType + " lock on: " + key);
4827 23 Mar 09 nicklas 615         rwLock = null;
4827 23 Mar 09 nicklas 616       }
4827 23 Mar 09 nicklas 617     }
4827 23 Mar 09 nicklas 618     catch (InterruptedException ex)
4827 23 Mar 09 nicklas 619     {
4827 23 Mar 09 nicklas 620       log.debug("Interrupted while waiting for " + lockType + " lock on: " + key);
7352 28 Apr 17 nicklas 621       Thread.currentThread().interrupt();
4827 23 Mar 09 nicklas 622       rwLock = null;
4827 23 Mar 09 nicklas 623     }
4827 23 Mar 09 nicklas 624     if (rwLock != null) log.debug("Got " + lockType + " lock on: " + key);
4827 23 Mar 09 nicklas 625     return rwLock;
4827 23 Mar 09 nicklas 626   }
4826 20 Mar 09 nicklas 627   
4827 23 Mar 09 nicklas 628   /**
4827 23 Mar 09 nicklas 629     A lock-safe output stream that releases the associated 
4827 23 Mar 09 nicklas 630     write lock when the stream is closed.
4827 23 Mar 09 nicklas 631     <p>
4827 23 Mar 09 nicklas 632     NOTE! It is important that we keep a strong reference
4827 23 Mar 09 nicklas 633     to the 'key' in this class until the stream is closed, 
4827 23 Mar 09 nicklas 634     since otherwise the locked entry may be reclaimed by the 
4827 23 Mar 09 nicklas 635     garbage collector.
4827 23 Mar 09 nicklas 636   */
4827 23 Mar 09 nicklas 637   static class LockSafeOutputStream
4827 23 Mar 09 nicklas 638     extends FilterOutputStream
4827 23 Mar 09 nicklas 639   {
7713 21 May 19 nicklas 640     private final State state;
7713 21 May 19 nicklas 641     private final Cleanable cleanable;
7713 21 May 19 nicklas 642     private final String key;
4827 23 Mar 09 nicklas 643     private boolean closed;
4827 23 Mar 09 nicklas 644
4827 23 Mar 09 nicklas 645     LockSafeOutputStream(OutputStream out, LockEntry lock)
4827 23 Mar 09 nicklas 646     {
4827 23 Mar 09 nicklas 647       super(out);
7713 21 May 19 nicklas 648       state = new State(out, lock, false);
7713 21 May 19 nicklas 649       state.calledFrom = new Throwable();
7715 22 May 19 nicklas 650       cleanable = Application.registerCleanup(this, state);
7713 21 May 19 nicklas 651       key = lock.getKey();
4827 23 Mar 09 nicklas 652     }
4827 23 Mar 09 nicklas 653
4827 23 Mar 09 nicklas 654     @Override
4827 23 Mar 09 nicklas 655     public void close() 
4827 23 Mar 09 nicklas 656       throws IOException
4827 23 Mar 09 nicklas 657     {
4827 23 Mar 09 nicklas 658       if (closed) return;
4827 23 Mar 09 nicklas 659       log.debug("Releasing write lock on: " + key);
7713 21 May 19 nicklas 660       state.calledFrom = null; // Set to null to not get a warning in the log file
7715 22 May 19 nicklas 661       if (cleanable != null) cleanable.clean();
4827 23 Mar 09 nicklas 662       closed = true;
4827 23 Mar 09 nicklas 663     }
4827 23 Mar 09 nicklas 664   
4827 23 Mar 09 nicklas 665     @Override
4827 23 Mar 09 nicklas 666     public String toString()
4827 23 Mar 09 nicklas 667     {
4827 23 Mar 09 nicklas 668       return "LockSafeOutputStream[" + key + "]";
4827 23 Mar 09 nicklas 669     }
4827 23 Mar 09 nicklas 670
4827 23 Mar 09 nicklas 671   }
4827 23 Mar 09 nicklas 672   
4827 23 Mar 09 nicklas 673   
4827 23 Mar 09 nicklas 674   /**
4827 23 Mar 09 nicklas 675     A lock-safe input stream that releases the associated 
4827 23 Mar 09 nicklas 676     read lock when the stream is closed.
4827 23 Mar 09 nicklas 677     <p>
4827 23 Mar 09 nicklas 678     NOTE! It is important that we keep a strong reference
4827 23 Mar 09 nicklas 679     to the 'key' in this class until the stream is closed, 
4827 23 Mar 09 nicklas 680     since otherwise the locked entry may be reclaimed by the 
4827 23 Mar 09 nicklas 681     garbage collector.
4827 23 Mar 09 nicklas 682   */
4827 23 Mar 09 nicklas 683   static class LockSafeInputStream
4827 23 Mar 09 nicklas 684     extends FilterInputStream
4827 23 Mar 09 nicklas 685   {
4827 23 Mar 09 nicklas 686   
7713 21 May 19 nicklas 687     private final State state;
7713 21 May 19 nicklas 688     private final Cleanable cleanable;
7713 21 May 19 nicklas 689     private final String key;
4827 23 Mar 09 nicklas 690     private boolean closed;
4827 23 Mar 09 nicklas 691   
4827 23 Mar 09 nicklas 692     LockSafeInputStream(InputStream in, LockEntry lock)
4827 23 Mar 09 nicklas 693     {
4827 23 Mar 09 nicklas 694       super(in);
7713 21 May 19 nicklas 695       state = new State(in, lock, true);
7713 21 May 19 nicklas 696       state.calledFrom = new Throwable();
7715 22 May 19 nicklas 697       cleanable = Application.registerCleanup(this, state);
7713 21 May 19 nicklas 698       key = lock.getKey();
4827 23 Mar 09 nicklas 699     }
4827 23 Mar 09 nicklas 700   
4827 23 Mar 09 nicklas 701     @Override
4827 23 Mar 09 nicklas 702     public void close() 
4827 23 Mar 09 nicklas 703       throws IOException
4827 23 Mar 09 nicklas 704     {
4827 23 Mar 09 nicklas 705       if (closed) return;
4827 23 Mar 09 nicklas 706       log.debug("Releasing read lock on: " + key);
7713 21 May 19 nicklas 707       state.calledFrom = null; // Set to null to not get a warning in the log file
7715 22 May 19 nicklas 708       if (cleanable != null) cleanable.clean();
4827 23 Mar 09 nicklas 709       closed = true;
4827 23 Mar 09 nicklas 710     }
4827 23 Mar 09 nicklas 711     
4827 23 Mar 09 nicklas 712     @Override
4827 23 Mar 09 nicklas 713     public String toString()
4827 23 Mar 09 nicklas 714     {
4827 23 Mar 09 nicklas 715       return "LockSafeInputStream[" + key + "]";
4827 23 Mar 09 nicklas 716     }
4827 23 Mar 09 nicklas 717   }
4827 23 Mar 09 nicklas 718
4827 23 Mar 09 nicklas 719   /**
4827 23 Mar 09 nicklas 720     Keeps track of a locked cached entry. The 'key' is the same key
4827 23 Mar 09 nicklas 721     instance that was used to create the entry in the first place.
4827 23 Mar 09 nicklas 722     It is important that all active maintainers of a lock also
4827 23 Mar 09 nicklas 723     keeps a strong reference to the key since the lock entry may
4827 23 Mar 09 nicklas 724     otherwise be reclaimed by the garbage collector.
4827 23 Mar 09 nicklas 725   */
4827 23 Mar 09 nicklas 726   static class LockEntry
4827 23 Mar 09 nicklas 727     implements ReadWriteLock
4827 23 Mar 09 nicklas 728   {
4827 23 Mar 09 nicklas 729     private ReadWriteLock rwLock;
4827 23 Mar 09 nicklas 730     private WeakReference<String> keyRef;
4827 23 Mar 09 nicklas 731     
4827 23 Mar 09 nicklas 732     LockEntry(ReadWriteLock rwLock, String key)
4827 23 Mar 09 nicklas 733     {
4827 23 Mar 09 nicklas 734       this.rwLock = rwLock;
4827 23 Mar 09 nicklas 735       this.keyRef = new WeakReference<String>(key);
4827 23 Mar 09 nicklas 736     }
4827 23 Mar 09 nicklas 737     
4827 23 Mar 09 nicklas 738     @Override
4827 23 Mar 09 nicklas 739     public Lock writeLock()
4827 23 Mar 09 nicklas 740     {
4827 23 Mar 09 nicklas 741       return rwLock.writeLock();
4827 23 Mar 09 nicklas 742     }
4827 23 Mar 09 nicklas 743     
4827 23 Mar 09 nicklas 744     @Override
4827 23 Mar 09 nicklas 745     public Lock readLock()
4827 23 Mar 09 nicklas 746     {
4827 23 Mar 09 nicklas 747       return rwLock.readLock();
4827 23 Mar 09 nicklas 748     }
4827 23 Mar 09 nicklas 749     
4827 23 Mar 09 nicklas 750     public String getKey()
4827 23 Mar 09 nicklas 751     {
4827 23 Mar 09 nicklas 752       return keyRef.get();
4827 23 Mar 09 nicklas 753     }
4827 23 Mar 09 nicklas 754     
4827 23 Mar 09 nicklas 755     @Override
4827 23 Mar 09 nicklas 756     public String toString()
4827 23 Mar 09 nicklas 757     {
4827 23 Mar 09 nicklas 758       return "LockEntry[" + getKey() + "]";
4827 23 Mar 09 nicklas 759     }
4827 23 Mar 09 nicklas 760   }
4827 23 Mar 09 nicklas 761   
4827 23 Mar 09 nicklas 762   /**
7713 21 May 19 nicklas 763     Inner class for performing the actual release of locks and closing of streams (in/out).
7713 21 May 19 nicklas 764   */
7713 21 May 19 nicklas 765   static class State
7713 21 May 19 nicklas 766     implements Runnable
7713 21 May 19 nicklas 767   {
7713 21 May 19 nicklas 768     
7713 21 May 19 nicklas 769     final Closeable stream;
7713 21 May 19 nicklas 770     final LockEntry lock;
7713 21 May 19 nicklas 771     final boolean readLock;
7713 21 May 19 nicklas 772     Throwable calledFrom;
7713 21 May 19 nicklas 773     
7713 21 May 19 nicklas 774     State(Closeable stream, LockEntry lock, boolean readLock) 
7713 21 May 19 nicklas 775     {
7713 21 May 19 nicklas 776       this.stream = stream;
7713 21 May 19 nicklas 777       this.lock = lock;
7713 21 May 19 nicklas 778       this.readLock = readLock;
7713 21 May 19 nicklas 779     }
7713 21 May 19 nicklas 780     
7713 21 May 19 nicklas 781     @Override
7713 21 May 19 nicklas 782     public void run() 
7713 21 May 19 nicklas 783     {
7713 21 May 19 nicklas 784       if (calledFrom != null)
7713 21 May 19 nicklas 785       {
7713 21 May 19 nicklas 786         log.warn("Found unreleased " + (readLock ? "read" : "write") + " lock on: " + lock.getKey(), calledFrom);
7713 21 May 19 nicklas 787       }
7713 21 May 19 nicklas 788       if (stream != null) FileUtil.close(stream);
7713 21 May 19 nicklas 789       if (readLock)
7713 21 May 19 nicklas 790       {
7713 21 May 19 nicklas 791         lock.readLock().unlock();
7713 21 May 19 nicklas 792       }
7713 21 May 19 nicklas 793       else
7713 21 May 19 nicklas 794       {
7713 21 May 19 nicklas 795         lock.writeLock().unlock();
7713 21 May 19 nicklas 796       }
7713 21 May 19 nicklas 797     }
7713 21 May 19 nicklas 798   }
7713 21 May 19 nicklas 799
7713 21 May 19 nicklas 800   
7713 21 May 19 nicklas 801   /**
4827 23 Mar 09 nicklas 802     A timer task that clean up the cache when it is executed.
4827 23 Mar 09 nicklas 803   */
5384 13 Aug 10 nicklas 804   public static class CleanupTask
4827 23 Mar 09 nicklas 805     extends TimerTask
4827 23 Mar 09 nicklas 806   {
4827 23 Mar 09 nicklas 807
4827 23 Mar 09 nicklas 808     private final StaticCache cache;
4827 23 Mar 09 nicklas 809     private final FileFilter filter;
4827 23 Mar 09 nicklas 810     
4827 23 Mar 09 nicklas 811     public CleanupTask(StaticCache cache, FileFilter filter)
4827 23 Mar 09 nicklas 812     {
4827 23 Mar 09 nicklas 813       this.cache = cache;
4827 23 Mar 09 nicklas 814       this.filter = filter;
4827 23 Mar 09 nicklas 815     }
4827 23 Mar 09 nicklas 816     
4827 23 Mar 09 nicklas 817     @Override
4827 23 Mar 09 nicklas 818     public void run()
4827 23 Mar 09 nicklas 819     {
4827 23 Mar 09 nicklas 820       cache.cleanUp(filter);
4827 23 Mar 09 nicklas 821     }
4827 23 Mar 09 nicklas 822   }
4826 20 Mar 09 nicklas 823 }