Clover coverage report -
Coverage timestamp: So Nov 6 2005 14:19:51 CET
file stats: LOC: 510   Methods: 22
NCLOC: 249   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AbstractDiskPersistenceListener.java 57,9% 72,8% 77,3% 70,3%
coverage coverage
 1    /*
 2    * Copyright (c) 2002-2003 by OpenSymphony
 3    * All rights reserved.
 4    */
 5    package com.opensymphony.oscache.plugins.diskpersistence;
 6   
 7    import com.opensymphony.oscache.base.Config;
 8    import com.opensymphony.oscache.base.persistence.CachePersistenceException;
 9    import com.opensymphony.oscache.base.persistence.PersistenceListener;
 10    import com.opensymphony.oscache.web.ServletCacheAdministrator;
 11   
 12    import org.apache.commons.logging.Log;
 13    import org.apache.commons.logging.LogFactory;
 14   
 15    import java.io.*;
 16   
 17    import java.util.Set;
 18   
 19    import javax.servlet.jsp.PageContext;
 20   
 21    /**
 22    * Persist the cache data to disk.
 23    *
 24    * The code in this class is totally not thread safe it is the resonsibility
 25    * of the cache using this persistence listener to handle the concurrency.
 26    *
 27    * @author <a href="mailto:fbeauregard@pyxis-tech.com">Francois Beauregard</a>
 28    * @author <a href="mailto:abergevin@pyxis-tech.com">Alain Bergevin</a>
 29    * @author <a href="&#109;a&#105;&#108;&#116;&#111;:chris&#64;swebtec.&#99;&#111;&#109;">Chris Miller</a>
 30    * @author <a href="mailto:amarch@soe.sony.com">Andres March</a>
 31    */
 32    public abstract class AbstractDiskPersistenceListener implements PersistenceListener, Serializable {
 33    public final static String CACHE_PATH_KEY = "cache.path";
 34   
 35    /**
 36    * File extension for disk cache file
 37    */
 38    protected final static String CACHE_EXTENSION = "cache";
 39   
 40    /**
 41    * The directory that cache groups are stored under
 42    */
 43    protected final static String GROUP_DIRECTORY = "__groups__";
 44   
 45    /**
 46    * Sub path name for application cache
 47    */
 48    protected final static String APPLICATION_CACHE_SUBPATH = "application";
 49   
 50    /**
 51    * Sub path name for session cache
 52    */
 53    protected final static String SESSION_CACHE_SUBPATH = "session";
 54   
 55    /**
 56    * Property to get the temporary working directory of the servlet container.
 57    */
 58    protected static final String CONTEXT_TMPDIR = "javax.servlet.context.tempdir";
 59    private static transient final Log log = LogFactory.getLog(AbstractDiskPersistenceListener.class);
 60   
 61    /**
 62    * Base path where the disk cache reside.
 63    */
 64    private File cachePath = null;
 65    private File contextTmpDir;
 66   
 67    /**
 68    * Root path for disk cache
 69    */
 70    private String root = null;
 71   
 72    /**
 73    * Get the physical cache path on disk.
 74    *
 75    * @return A file representing the physical cache location.
 76    */
 77  78 public File getCachePath() {
 78  78 return cachePath;
 79    }
 80   
 81    /**
 82    * Get the root directory for persisting the cache on disk.
 83    * This path includes scope and sessionId, if any.
 84    *
 85    * @return A String representing the root directory.
 86    */
 87  0 public String getRoot() {
 88  0 return root;
 89    }
 90   
 91    /**
 92    * Get the servlet context tmp directory.
 93    *
 94    * @return A file representing the servlet context tmp directory.
 95    */
 96  0 public File getContextTmpDir() {
 97  0 return contextTmpDir;
 98    }
 99   
 100    /**
 101    * Verify if a group exists in the cache
 102    *
 103    * @param group The group name to check
 104    * @return True if it exists
 105    * @throws CachePersistenceException
 106    */
 107  0 public boolean isGroupStored(String group) throws CachePersistenceException {
 108  0 try {
 109  0 File file = getCacheGroupFile(group);
 110   
 111  0 return file.exists();
 112    } catch (Exception e) {
 113  0 throw new CachePersistenceException("Unable verify group '" + group + "' exists in the cache: " + e);
 114    }
 115    }
 116   
 117    /**
 118    * Verify if an object is currently stored in the cache
 119    *
 120    * @param key The object key
 121    * @return True if it exists
 122    * @throws CachePersistenceException
 123    */
 124  64 public boolean isStored(String key) throws CachePersistenceException {
 125  64 try {
 126  64 File file = getCacheFile(key);
 127   
 128  64 return file.exists();
 129    } catch (Exception e) {
 130  0 throw new CachePersistenceException("Unable verify id '" + key + "' is stored in the cache: " + e);
 131    }
 132    }
 133   
 134    /**
 135    * Clears the whole cache directory, starting from the root
 136    *
 137    * @throws CachePersistenceException
 138    */
 139  85 public void clear() throws CachePersistenceException {
 140  85 clear(root);
 141    }
 142   
 143    /**
 144    * Initialises this <tt>DiskPersistenceListener</tt> using the supplied
 145    * configuration.
 146    *
 147    * @param config The OSCache configuration
 148    */
 149  78 public PersistenceListener configure(Config config) {
 150  78 String sessionId = null;
 151  78 int scope = 0;
 152  78 initFileCaching(config.getProperty(CACHE_PATH_KEY));
 153   
 154  78 if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID) != null) {
 155  0 sessionId = config.getProperty(ServletCacheAdministrator.HASH_KEY_SESSION_ID);
 156    }
 157   
 158  78 if (config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE) != null) {
 159  0 scope = Integer.parseInt(config.getProperty(ServletCacheAdministrator.HASH_KEY_SCOPE));
 160    }
 161   
 162  78 StringBuffer root = new StringBuffer(getCachePath().getPath());
 163  78 root.append("/");
 164  78 root.append(getPathPart(scope));
 165   
 166  78 if ((sessionId != null) && (sessionId.length() > 0)) {
 167  0 root.append("/");
 168  0 root.append(sessionId);
 169    }
 170   
 171  78 this.root = root.toString();
 172  78 this.contextTmpDir = (File) config.get(ServletCacheAdministrator.HASH_KEY_CONTEXT_TMPDIR);
 173   
 174  78 return this;
 175    }
 176   
 177    /**
 178    * Delete a single cache entry.
 179    *
 180    * @param key The object key to delete
 181    * @throws CachePersistenceException
 182    */
 183  21 public void remove(String key) throws CachePersistenceException {
 184  21 File file = getCacheFile(key);
 185  21 remove(file);
 186    }
 187   
 188    /**
 189    * Deletes an entire group from the cache.
 190    *
 191    * @param groupName The name of the group to delete
 192    * @throws CachePersistenceException
 193    */
 194  0 public void removeGroup(String groupName) throws CachePersistenceException {
 195  0 File file = getCacheGroupFile(groupName);
 196  0 remove(file);
 197    }
 198   
 199    /**
 200    * Retrieve an object from the disk
 201    *
 202    * @param key The object key
 203    * @return The retrieved object
 204    * @throws CachePersistenceException
 205    */
 206  380 public Object retrieve(String key) throws CachePersistenceException {
 207  380 return retrieve(getCacheFile(key));
 208    }
 209   
 210    /**
 211    * Retrieves a group from the cache, or <code>null</code> if the group
 212    * file could not be found.
 213    *
 214    * @param groupName The name of the group to retrieve.
 215    * @return A <code>Set</code> containing keys of all of the cache
 216    * entries that belong to this group.
 217    * @throws CachePersistenceException
 218    */
 219  109 public Set retrieveGroup(String groupName) throws CachePersistenceException {
 220  109 File groupFile = getCacheGroupFile(groupName);
 221   
 222  109 try {
 223  109 return (Set) retrieve(groupFile);
 224    } catch (ClassCastException e) {
 225  0 throw new CachePersistenceException("Group file " + groupFile + " was not persisted as a Set: " + e);
 226    }
 227    }
 228   
 229    /**
 230    * Stores an object in cache
 231    *
 232    * @param key The object's key
 233    * @param obj The object to store
 234    * @throws CachePersistenceException
 235    */
 236  182 public void store(String key, Object obj) throws CachePersistenceException {
 237  182 File file = getCacheFile(key);
 238  182 store(file, obj);
 239    }
 240   
 241    /**
 242    * Stores a group in the persistent cache. This will overwrite any existing
 243    * group with the same name
 244    */
 245  98 public void storeGroup(String groupName, Set group) throws CachePersistenceException {
 246  98 File groupFile = getCacheGroupFile(groupName);
 247  98 store(groupFile, group);
 248    }
 249   
 250    /**
 251    * Allows to translate to the temp dir of the servlet container if cachePathStr
 252    * is javax.servlet.context.tempdir.
 253    *
 254    * @param cachePathStr Cache path read from the properties file.
 255    * @return Adjusted cache path
 256    */
 257  0 protected String adjustFileCachePath(String cachePathStr) {
 258  0 if (cachePathStr.compareToIgnoreCase(CONTEXT_TMPDIR) == 0) {
 259  0 cachePathStr = contextTmpDir.getAbsolutePath();
 260    }
 261   
 262  0 return cachePathStr;
 263    }
 264   
 265    /**
 266    * Set caching to file on or off.
 267    * If the <code>cache.path</code> property exists, we assume file caching is turned on.
 268    * By the same token, to turn off file caching just remove this property.
 269    */
 270  78 protected void initFileCaching(String cachePathStr) {
 271  78 if (cachePathStr != null) {
 272  78 cachePath = new File(cachePathStr);
 273   
 274  78 try {
 275  78 if (!cachePath.exists()) {
 276  3 if (log.isInfoEnabled()) {
 277  3 log.info("cache.path '" + cachePathStr + "' does not exist, creating");
 278    }
 279   
 280  3 cachePath.mkdirs();
 281    }
 282   
 283  78 if (!cachePath.isDirectory()) {
 284  0 log.error("cache.path '" + cachePathStr + "' is not a directory");
 285  0 cachePath = null;
 286  78 } else if (!cachePath.canWrite()) {
 287  0 log.error("cache.path '" + cachePathStr + "' is not a writable location");
 288  0 cachePath = null;
 289    }
 290    } catch (Exception e) {
 291  0 log.error("cache.path '" + cachePathStr + "' could not be used", e);
 292  0 cachePath = null;
 293    }
 294    } else {
 295    // Use default value
 296    }
 297    }
 298   
 299  21 protected void remove(File file) throws CachePersistenceException {
 300  21 try {
 301    // Loop until we are able to delete (No current read).
 302    // The cache must ensure that there are never two concurrent threads
 303    // doing write (store and delete) operations on the same item.
 304    // Delete only should be enough but file.exists prevents infinite loop
 305  21 while (!file.delete() && file.exists()) {
 306    ;
 307    }
 308    } catch (Exception e) {
 309  0 throw new CachePersistenceException("Unable to remove '" + file + "' from the cache: " + e);
 310    }
 311    }
 312   
 313    /**
 314    * Stores an object using the supplied file object
 315    *
 316    * @param file The file to use for storing the object
 317    * @param obj the object to store
 318    * @throws CachePersistenceException
 319    */
 320  280 protected void store(File file, Object obj) throws CachePersistenceException {
 321    // check if the directory structure required exists and create it if it doesn't
 322  280 File filepath = new File(file.getParent());
 323   
 324  280 try {
 325  280 if (!filepath.exists()) {
 326  27 filepath.mkdirs();
 327    }
 328    } catch (Exception e) {
 329  0 throw new CachePersistenceException("Unable to create the directory " + filepath);
 330    }
 331   
 332    // Loop until we are able to delete (No current read).
 333    // The cache must ensure that there are never two concurrent threads
 334    // doing write (store and delete) operations on the same item.
 335    // Delete only should be enough but file.exists prevents infinite loop
 336  280 while (file.exists() && !file.delete()) {
 337    ;
 338    }
 339   
 340    // Write the object to disk
 341  280 FileOutputStream fout = null;
 342  280 ObjectOutputStream oout = null;
 343   
 344  280 try {
 345  280 fout = new FileOutputStream(file);
 346  280 try {
 347  280 oout = new ObjectOutputStream(fout);
 348  280 try {
 349  280 oout.writeObject(obj);
 350  280 oout.flush();
 351    } finally {
 352  280 try {
 353  280 oout.close();
 354    } catch (Exception e) {
 355    }
 356    }
 357    } finally {
 358  280 try {
 359  280 fout.close();
 360    } catch (Exception e) {
 361    }
 362    }
 363    } catch (Exception e) {
 364  0 while (file.exists() && !file.delete()) {
 365    ;
 366    }
 367  0 throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage());
 368    }
 369    }
 370   
 371    /**
 372    * Build fully qualified cache file for the specified cache entry key.
 373    *
 374    * @param key Cache Entry Key.
 375    * @return File reference.
 376    */
 377  647 protected File getCacheFile(String key) {
 378  647 char[] fileChars = getCacheFileName(key);
 379   
 380  647 File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION);
 381   
 382  647 return file;
 383    }
 384   
 385    /**
 386    * Build cache file name for the specified cache entry key.
 387    *
 388    * @param key Cache Entry Key.
 389    * @return char[] file name.
 390    */
 391    protected abstract char[] getCacheFileName(String key);
 392   
 393    /**
 394    * Builds a fully qualified file name that specifies a cache group entry.
 395    *
 396    * @param group The name of the group
 397    * @return A File reference
 398    */
 399  207 private File getCacheGroupFile(String group) {
 400  207 int AVERAGE_PATH_LENGTH = 30;
 401   
 402  207 if ((group == null) || (group.length() == 0)) {
 403  0 throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile.");
 404    }
 405   
 406  207 StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
 407   
 408    // Build a fully qualified file name for this group
 409  207 path.append(GROUP_DIRECTORY).append('/');
 410  207 path.append(group).append('.').append(CACHE_EXTENSION);
 411   
 412  207 return new File(root, path.toString());
 413    }
 414   
 415    /**
 416    * This allows to persist different scopes in different path in the case of
 417    * file caching.
 418    *
 419    * @param scope Cache scope.
 420    * @return The scope subpath
 421    */
 422  78 private String getPathPart(int scope) {
 423  78 if (scope == PageContext.SESSION_SCOPE) {
 424  0 return SESSION_CACHE_SUBPATH;
 425    } else {
 426  78 return APPLICATION_CACHE_SUBPATH;
 427    }
 428    }
 429   
 430    /**
 431    * Clears a whole directory, starting from the specified
 432    * directory
 433    *
 434    * @param baseDirName The root directory to delete
 435    * @throws CachePersistenceException
 436    */
 437  85 private void clear(String baseDirName) throws CachePersistenceException {
 438  85 File baseDir = new File(baseDirName);
 439  85 File[] fileList = baseDir.listFiles();
 440   
 441  85 try {
 442  85 if (fileList != null) {
 443    // Loop through all the files and directory to delete them
 444  22 for (int count = 0; count < fileList.length; count++) {
 445  22 if (fileList[count].isFile()) {
 446  22 fileList[count].delete();
 447    } else {
 448    // Make a recursive call to delete the directory
 449  0 clear(fileList[count].toString());
 450  0 fileList[count].delete();
 451    }
 452    }
 453    }
 454   
 455    // Delete the root directory
 456  85 baseDir.delete();
 457    } catch (Exception e) {
 458  0 throw new CachePersistenceException("Unable to clear the cache directory");
 459    }
 460    }
 461   
 462    /**
 463    * Retrives a serialized object from the supplied file, or returns
 464    * <code>null</code> if the file does not exist.
 465    *
 466    * @param file The file to deserialize
 467    * @return The deserialized object
 468    * @throws CachePersistenceException
 469    */
 470  489 private Object retrieve(File file) throws CachePersistenceException {
 471  489 Object readContent = null;
 472  489 boolean fileExist;
 473   
 474  489 try {
 475  489 fileExist = file.exists();
 476    } catch (Exception e) {
 477  0 throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e);
 478    }
 479   
 480    // Read the file if it exists
 481  489 if (fileExist) {
 482  269 BufferedInputStream in = null;
 483  269 ObjectInputStream oin = null;
 484   
 485  269 try {
 486  269 in = new BufferedInputStream(new FileInputStream(file));
 487  269 oin = new ObjectInputStream(in);
 488  269 readContent = oin.readObject();
 489    } catch (Exception e) {
 490    // We expect this exception to occur.
 491    // This is when the item will be invalidated (written or deleted)
 492    // during read.
 493    // The cache has the logic to retry reading.
 494  0 throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e);
 495    } finally {
 496  269 try {
 497  269 oin.close();
 498    } catch (Exception ex) {
 499    }
 500   
 501  269 try {
 502  269 in.close();
 503    } catch (Exception ex) {
 504    }
 505    }
 506    }
 507   
 508  489 return readContent;
 509    }
 510    }