Clover coverage report -
Coverage timestamp: Sat Apr 30 2005 21:58:28 PDT
file stats: LOC: 508   Methods: 22
NCLOC: 244   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AbstractDiskPersistenceListener.java 57.9% 72.4% 77.3% 69.9%
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  73
     public void clear() throws CachePersistenceException {
 140  73
         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  22
     public void remove(String key) throws CachePersistenceException {
 184  22
         File file = getCacheFile(key);
 185  22
         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  383
     public Object retrieve(String key) throws CachePersistenceException {
 207  383
         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  4
                     if (log.isInfoEnabled()) {
 277  4
                         log.info("cache.path '" + cachePathStr + "' does not exist, creating");
 278   
                     }
 279   
 
 280  4
                     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  22
     protected void remove(File file) throws CachePersistenceException {
 300  22
         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  22
             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
             oout = new ObjectOutputStream(fout);
 347  280
             oout.writeObject(obj);
 348  280
             oout.flush();
 349   
         } catch (Exception e) {
 350  0
             while (file.exists() && !file.delete()) {
 351   
                 ;
 352   
             }
 353   
 
 354  0
             throw new CachePersistenceException("Unable to write '" + file + "' in the cache. Exception: " + e.getClass().getName() + ", Message: " + e.getMessage());
 355   
         } finally {
 356  280
             try {
 357  280
                 fout.close();
 358   
             } catch (Exception e) {
 359   
             }
 360   
 
 361  280
             try {
 362  280
                 oout.close();
 363   
             } catch (Exception e) {
 364   
             }
 365   
         }
 366   
     }
 367   
 
 368   
     /**
 369   
     * Build fully qualified cache file for the specified cache entry key.
 370   
     *
 371   
     * @param key   Cache Entry Key.
 372   
     * @return File reference.
 373   
     */
 374  653
     protected File getCacheFile(String key) {
 375  651
         char[] fileChars = getCacheFileName(key);
 376   
 
 377  653
         File file = new File(root, new String(fileChars) + "." + CACHE_EXTENSION);
 378   
 
 379  627
         return file;
 380   
     }
 381   
 
 382   
     /**
 383   
     * Build cache file name for the specified cache entry key.
 384   
     *
 385   
     * @param key   Cache Entry Key.
 386   
     * @return char[] file name.
 387   
     */
 388   
     protected abstract char[] getCacheFileName(String key);
 389   
 
 390   
     /**
 391   
     * Builds a fully qualified file name that specifies a cache group entry.
 392   
     *
 393   
     * @param group The name of the group
 394   
     * @return A File reference
 395   
     */
 396  207
     private File getCacheGroupFile(String group) {
 397  207
         int AVERAGE_PATH_LENGTH = 30;
 398   
 
 399  207
         if ((group == null) || (group.length() == 0)) {
 400  0
             throw new IllegalArgumentException("Invalid group '" + group + "' specified to getCacheGroupFile.");
 401   
         }
 402   
 
 403  207
         StringBuffer path = new StringBuffer(AVERAGE_PATH_LENGTH);
 404   
 
 405   
         // Build a fully qualified file name for this group
 406  207
         path.append(GROUP_DIRECTORY).append('/');
 407  207
         path.append(group).append('.').append(CACHE_EXTENSION);
 408   
 
 409  207
         return new File(root, path.toString());
 410   
     }
 411   
 
 412   
     /**
 413   
     * This allows to persist different scopes in different path in the case of
 414   
     * file caching.
 415   
     *
 416   
     * @param scope   Cache scope.
 417   
     * @return The scope subpath
 418   
     */
 419  78
     private String getPathPart(int scope) {
 420  78
         if (scope == PageContext.SESSION_SCOPE) {
 421  0
             return SESSION_CACHE_SUBPATH;
 422   
         } else {
 423  78
             return APPLICATION_CACHE_SUBPATH;
 424   
         }
 425   
     }
 426   
 
 427   
     /**
 428   
     * Clears a whole directory, starting from the specified
 429   
     * directory
 430   
     *
 431   
     * @param baseDirName The root directory to delete
 432   
     * @throws CachePersistenceException
 433   
     */
 434  73
     private void clear(String baseDirName) throws CachePersistenceException {
 435  73
         File baseDir = new File(baseDirName);
 436  73
         File[] fileList = baseDir.listFiles();
 437   
 
 438  73
         try {
 439  73
             if (fileList != null) {
 440   
                 // Loop through all the files and directory to delete them
 441  22
                 for (int count = 0; count < fileList.length; count++) {
 442  22
                     if (fileList[count].isFile()) {
 443  22
                         fileList[count].delete();
 444   
                     } else {
 445   
                         // Make a recursive call to delete the directory
 446  0
                         clear(fileList[count].toString());
 447  0
                         fileList[count].delete();
 448   
                     }
 449   
                 }
 450   
             }
 451   
 
 452   
             // Delete the root directory
 453  73
             baseDir.delete();
 454   
         } catch (Exception e) {
 455  0
             throw new CachePersistenceException("Unable to clear the cache directory");
 456   
         }
 457   
     }
 458   
 
 459   
     /**
 460   
     * Retrives a serialized object from the supplied file, or returns
 461   
     * <code>null</code> if the file does not exist.
 462   
     *
 463   
     * @param file The file to deserialize
 464   
     * @return The deserialized object
 465   
     * @throws CachePersistenceException
 466   
     */
 467  491
     private Object retrieve(File file) throws CachePersistenceException {
 468  492
         Object readContent = null;
 469  492
         boolean fileExist;
 470   
 
 471  491
         try {
 472  492
             fileExist = file.exists();
 473   
         } catch (Exception e) {
 474  0
             throw new CachePersistenceException("Unable to verify if " + file + " exists: " + e);
 475   
         }
 476   
 
 477   
         // Read the file if it exists
 478  494
         if (fileExist) {
 479  269
             BufferedInputStream in = null;
 480  269
             ObjectInputStream oin = null;
 481   
 
 482  269
             try {
 483  269
                 in = new BufferedInputStream(new FileInputStream(file));
 484  269
                 oin = new ObjectInputStream(in);
 485  269
                 readContent = oin.readObject();
 486   
             } catch (Exception e) {
 487   
                 // We expect this exception to occur.
 488   
                 // This is when the item will be invalidated (written or deleted)
 489   
                 // during read.
 490   
                 // The cache has the logic to retry reading.
 491  0
                 throw new CachePersistenceException("Unable to read '" + file.getAbsolutePath() + "' from the cache: " + e);
 492   
             } finally {
 493  269
                 try {
 494  269
                     oin.close();
 495   
                 } catch (Exception ex) {
 496   
                 }
 497   
 
 498  269
                 try {
 499  269
                     in.close();
 500   
                 } catch (Exception ex) {
 501   
                 }
 502   
             }
 503   
         }
 504   
 
 505  494
         return readContent;
 506   
     }
 507   
 }
 508