Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 548   Methods: 29
NCLOC: 435   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
FileCacheLoader.java 81.8% 92.9% 96.6% 90%
coverage coverage
 1    package org.jboss.cache.loader;
 2   
 3    import net.jcip.annotations.ThreadSafe;
 4    import org.apache.commons.logging.Log;
 5    import org.apache.commons.logging.LogFactory;
 6    import org.jboss.cache.Fqn;
 7    import org.jboss.cache.Modification;
 8    import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
 9    import org.jboss.cache.lock.StripedLock;
 10    import org.jboss.cache.marshall.ObjectSerializationFactory;
 11   
 12    import java.io.File;
 13    import java.io.FileInputStream;
 14    import java.io.FileNotFoundException;
 15    import java.io.FileOutputStream;
 16    import java.io.IOException;
 17    import java.io.ObjectInputStream;
 18    import java.io.ObjectOutputStream;
 19    import java.util.Collections;
 20    import java.util.HashMap;
 21    import java.util.HashSet;
 22    import java.util.List;
 23    import java.util.Map;
 24    import java.util.Set;
 25    import java.util.concurrent.ConcurrentHashMap;
 26    import java.util.regex.Matcher;
 27    import java.util.regex.Pattern;
 28   
 29    /**
 30    * Simple file-based CacheLoader implementation. Nodes are directories, attributes of a node is a file in the directory
 31    * <p/>
 32    * The FileCacheLoader has some severe limitations which restrict it's use in a production
 33    * environment, or if used in such an environment, it should be used with due care and sufficient
 34    * understanding of these limitations.
 35    * <ul>
 36    * <li>Due to the way the FileCacheLoader represents a tree structure on disk (directories and files) traversal is inefficient for deep trees.</li>
 37    * <li>Usage on shared filesystems like NFS, Windows shares, etc. should be avoided as these do not implement proper file locking and can cause data corruption.</li>
 38    * <li>Usage with an isolation level of NONE can cause corrupt writes as multiple threads attempt to write to the same file.</li>
 39    * <li>File systems are inherently not transactional, so when attempting to use your cache in a transactional context, failures when writing to the file (which happens during the commit phase) cannot be recovered.</li>
 40    * </ul>
 41    * <p/>
 42    * As a rule of thumb, it is recommended that the FileCacheLoader not be used in a highly concurrent,
 43    * transactional or stressful environment, and it's use is restricted to testing.
 44    * <p/>
 45    * In terms of concurrency, file systems are notoriously inconsistent in their implementations of concurrent locks. To get around
 46    * this and to meet the <b>thread safety</b> contracts set out in {@link CacheLoader}, this implementation uses a {@link org.jboss.cache.lock.StripedLock}
 47    *
 48    * @author Bela Ban
 49    * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a>
 50    * @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
 51    * @version $Id: FileCacheLoader.java,v 1.35 2007/06/19 18:49:33 msurtani Exp $
 52    */
 53    @ThreadSafe
 54    public class FileCacheLoader extends AbstractCacheLoader
 55    {
 56    File root = null;
 57    String rootPath = null;
 58    Log log = LogFactory.getLog(getClass());
 59   
 60    protected final StripedLock lock = new StripedLock();
 61   
 62    private FileCacheLoaderConfig config;
 63   
 64    /**
 65    * HashMap<Object,List<Modification>>. List of open transactions. Note that this is purely transient, as
 66    * we don't use a log, recovery is not available
 67    */
 68    Map<Object, List<Modification>> transactions = new ConcurrentHashMap<Object, List<Modification>>();
 69   
 70    /**
 71    * CacheImpl data file.
 72    */
 73    public static final String DATA = "data.dat";
 74   
 75    /**
 76    * CacheImpl directory suffix.
 77    */
 78    public static final String DIR_SUFFIX = "fdb";
 79   
 80    /**
 81    * For full path, check '*' '<' '>' '|' '"' '?' Regex: [\*<>|"?]
 82    */
 83    public static final Pattern PATH_PATTERN = Pattern.compile("[\\*<>|\"?]");
 84   
 85    /**
 86    * For fqn, check '*' '<' '>' '|' '"' '?' and also '\' '/' and ':'
 87    */
 88    public static final Pattern FQN_PATTERN = Pattern.compile("[\\\\\\/:*<>|\"?]");
 89   
 90  364 public void setConfig(IndividualCacheLoaderConfig base)
 91    {
 92  364 if (base instanceof FileCacheLoaderConfig)
 93    {
 94  1 this.config = (FileCacheLoaderConfig) base;
 95    }
 96  363 else if (base != null)
 97    {
 98  363 this.config = new FileCacheLoaderConfig(base);
 99    }
 100   
 101  364 String location = this.config != null ? this.config.getLocation() : null;
 102  364 if (location != null && location.length() > 0)
 103    {
 104  313 root = new File(location);
 105  313 rootPath = root.getAbsolutePath() + File.separator;
 106    }
 107    }
 108   
 109  364 public IndividualCacheLoaderConfig getConfig()
 110    {
 111  364 return config;
 112    }
 113   
 114  334 public void create() throws Exception
 115    {
 116  334 lock.acquireLock(Fqn.ROOT, true);
 117  334 try
 118    {
 119  334 if (root == null)
 120    {
 121  52 String tmpLocation = System.getProperty("java.io.tmpdir", "C:\\tmp");
 122  52 root = new File(tmpLocation);
 123  52 rootPath = root.getAbsolutePath() + File.separator;
 124    }
 125  334 if (!root.exists())
 126    {
 127  204 if (log.isTraceEnabled())
 128    {
 129  0 log.trace("Creating cache loader location " + root);
 130    }
 131   
 132  204 if (config.isCheckCharacterPortability())
 133    {
 134    /* Before creating the root, check whether the path is character portable. Anything that comes after is part
 135    of the fqn which is inspected later. */
 136  204 isCharacterPortableLocation(root.getAbsolutePath());
 137    }
 138   
 139  204 boolean created = root.mkdirs();
 140  204 if (!created)
 141    {
 142  0 throw new IOException("Unable to create cache loader location " + root);
 143    }
 144    }
 145   
 146  334 if (!root.isDirectory())
 147    {
 148  0 throw new IOException("Cache loader location [" + root + "] is not a directory!");
 149    }
 150    }
 151    finally
 152    {
 153  334 lock.releaseLock(Fqn.ROOT);
 154    }
 155    }
 156   
 157  334 public void start() throws Exception
 158    {
 159    }
 160   
 161  334 public void stop()
 162    {
 163    }
 164   
 165  333 public void destroy()
 166    {
 167    }
 168   
 169  6467 public Set<String> getChildrenNames(Fqn fqn) throws Exception
 170    {
 171  6467 lock.acquireLock(fqn, true);
 172  6467 try
 173    {
 174  6467 File parent = getDirectory(fqn, false);
 175  2623 if (parent == null) return null;
 176  3844 File[] children = parent.listFiles();
 177  3844 Set<String> s = new HashSet<String>();
 178  3844 for (File child : children)
 179    {
 180  4900 if (child.isDirectory() && child.getName().endsWith(DIR_SUFFIX))
 181    {
 182  346 String child_name = child.getName();
 183  346 child_name = child_name.substring(0, child_name.lastIndexOf(DIR_SUFFIX) - 1);
 184  346 s.add(child_name);
 185    }
 186    }
 187  3844 return s.size() == 0 ? null : s;
 188    }
 189    finally
 190    {
 191  6467 lock.releaseLock(fqn);
 192    }
 193    }
 194   
 195  5872 public Map get(Fqn fqn) throws Exception
 196    {
 197  5872 lock.acquireLock(fqn, true);
 198  5872 try
 199    {
 200  5872 return loadAttributes(fqn);
 201    }
 202    finally
 203    {
 204  5872 lock.releaseLock(fqn);
 205    }
 206    }
 207   
 208  1920 public boolean exists(Fqn fqn) throws Exception
 209    {
 210  1920 lock.acquireLock(fqn, true);
 211  1920 try
 212    {
 213  1920 File f = getDirectory(fqn, false);
 214  1920 return f != null;
 215    }
 216    finally
 217    {
 218  1920 lock.releaseLock(fqn);
 219    }
 220    }
 221   
 222  7845 public Object put(Fqn fqn, Object key, Object value) throws Exception
 223    {
 224  7845 lock.acquireLock(fqn, true);
 225  7845 try
 226    {
 227  7845 Object retval;
 228  7845 Map m = loadAttributes(fqn);
 229  511 if (m == null) m = new HashMap();
 230  7845 retval = m.put(key, value);
 231  7845 storeAttributes(fqn, m);
 232  7845 return retval;
 233    }
 234    finally
 235    {
 236  7845 lock.releaseLock(fqn);
 237    }
 238    }
 239   
 240  6563 public void put(Fqn fqn, Map attributes) throws Exception
 241    {
 242  6563 put(fqn, attributes, false);
 243    }
 244   
 245   
 246  6649 public void put(Fqn fqn, Map attributes, boolean erase) throws Exception
 247    {
 248  6649 lock.acquireLock(fqn, true);
 249  6649 try
 250    {
 251  6649 Map m = erase ? new HashMap() : loadAttributes(fqn);
 252  632 if (m == null) m = new HashMap();
 253  6649 if (attributes != null)
 254    {
 255  6373 m.putAll(attributes);
 256    }
 257  6649 storeAttributes(fqn, m);
 258    }
 259    finally
 260    {
 261  6649 lock.releaseLock(fqn);
 262    }
 263    }
 264   
 265  0 void put(Fqn fqn) throws Exception
 266    {
 267  0 getDirectory(fqn, true);
 268    }
 269   
 270  6099 public Object remove(Fqn fqn, Object key) throws Exception
 271    {
 272  6099 lock.acquireLock(fqn, true);
 273  6099 try
 274    {
 275  6099 Object retval;
 276  6099 Map m = loadAttributes(fqn);
 277  3445 if (m == null) return null;
 278  2654 retval = m.remove(key);
 279  2654 storeAttributes(fqn, m);
 280  2654 return retval;
 281    }
 282    finally
 283    {
 284  6099 lock.releaseLock(fqn);
 285    }
 286    }
 287   
 288  6880 public void remove(Fqn fqn) throws Exception
 289    {
 290  6880 lock.acquireLock(fqn, true);
 291  6880 try
 292    {
 293  6880 File dir = getDirectory(fqn, false);
 294  6880 if (dir != null)
 295    {
 296  1018 boolean flag = removeDirectory(dir, true);
 297  1018 if (!flag)
 298    {
 299  0 log.warn("failed removing " + fqn);
 300    }
 301    }
 302    }
 303    finally
 304    {
 305  6880 lock.releaseLock(fqn);
 306    }
 307    }
 308   
 309  47 public void removeData(Fqn fqn) throws Exception
 310    {
 311  47 lock.acquireLock(fqn, true);
 312  47 try
 313    {
 314  47 File f = getDirectory(fqn, false);
 315  47 if (f != null)
 316    {
 317  18 File data = new File(f, DATA);
 318  18 if (data.exists())
 319    {
 320  18 boolean flag = data.delete();
 321  18 if (!flag)
 322    {
 323  0 log.warn("failed removing file " + data.getName());
 324    }
 325    }
 326    }
 327    }
 328    finally
 329    {
 330  47 lock.releaseLock(fqn);
 331    }
 332    }
 333   
 334  39 public void prepare(Object tx, List<Modification> modifications, boolean one_phase) throws Exception
 335    {
 336  39 if (one_phase)
 337    {
 338  14 put(modifications);
 339    }
 340    else
 341    {
 342  25 transactions.put(tx, modifications);
 343    }
 344    }
 345   
 346  17 public void commit(Object tx) throws Exception
 347    {
 348  17 List modifications = transactions.remove(tx);
 349  17 if (modifications == null)
 350    {
 351  0 throw new Exception("transaction " + tx + " not found in transaction table");
 352    }
 353  17 put(modifications);
 354    }
 355   
 356  8 public void rollback(Object tx)
 357    {
 358  8 transactions.remove(tx);
 359    }
 360   
 361    /* ----------------------- Private methods ------------------------ */
 362   
 363  58841 File getDirectory(Fqn fqn, boolean create)
 364    {
 365  58841 File f = new File(getFullPath(fqn));
 366  58841 if (!f.exists())
 367    {
 368  17005 if (create)
 369    {
 370  1229 f.mkdirs();
 371    }
 372    else
 373    {
 374  15776 return null;
 375    }
 376    }
 377  43065 return f;
 378    }
 379   
 380   
 381    /**
 382    * Recursively removes this and all subdirectories, plus all DATA files in them. To prevent damage, we only
 383    * remove files that are named DATA (data.dat) and directories which end in ".fdb". If there is a dir or file
 384    * that isn't named this way, the recursive removal will fail
 385    *
 386    * @return <code>true</code> if directory was removed,
 387    * <code>false</code> if not.
 388    */
 389  1727 boolean removeDirectory(File dir, boolean include_start_dir)
 390    {
 391  1727 boolean success = true;
 392  1727 File[] subdirs = dir.listFiles();
 393  1727 for (File file : subdirs)
 394    {
 395  5458 if (file.isFile() && file.getName().equals(DATA))
 396    {
 397  1036 if (!file.delete())
 398    {
 399  0 success = false;
 400    }
 401  1036 continue;
 402    }
 403  4422 if (file.isDirectory() && file.getName().endsWith(DIR_SUFFIX))
 404    {
 405  709 if (!removeDirectory(file, false))
 406    {
 407  0 success = false;
 408    }
 409  709 if (!file.delete())
 410    {
 411  0 success = false;
 412    }
 413    }
 414    }
 415   
 416  1727 if (include_start_dir)
 417    {
 418  1018 if (!dir.equals(root))
 419    {
 420  738 if (dir.delete())
 421    {
 422  738 return success;
 423    }
 424  0 success = false;
 425    }
 426    }
 427   
 428  989 return success;
 429    }
 430   
 431  58841 String getFullPath(Fqn fqn)
 432    {
 433  58841 StringBuffer sb = new StringBuffer(rootPath);
 434  58841 for (int i = 0; i < fqn.size(); i++)
 435    {
 436  207217 Object tmp = fqn.get(i);
 437    // This is where we convert from Object to String!
 438  207217 String tmp_dir = tmp.toString(); // returns tmp.this if it's a String
 439  207217 sb.append(tmp_dir).append(".").append(DIR_SUFFIX).append(File.separator);
 440    }
 441  58841 return sb.toString();
 442    }
 443   
 444  26379 protected Map loadAttributes(Fqn fqn) throws Exception
 445    {
 446  26379 File f = getDirectory(fqn, false);
 447  5676 if (f == null) return null; // i.e., this node does not exist.
 448    // this node exists so we should never return a null after this... at worst case, an empty HashMap.
 449  20703 File child = new File(f, DATA);
 450  189 if (!child.exists()) return new HashMap(0); // no node attribs exist hence the empty HashMap.
 451    //if(!child.exists()) return null;
 452   
 453  20514 Map m = null;
 454  20514 try
 455    {
 456  20514 m = (Map) unmarshall(child);
 457    }
 458    catch (FileNotFoundException fnfe)
 459    {
 460    // child no longer exists!
 461  0 m = Collections.emptyMap();
 462    }
 463  20514 return m;
 464    }
 465   
 466  17148 protected void storeAttributes(Fqn fqn, Map attrs) throws Exception
 467    {
 468  17148 File f = getDirectory(fqn, true);
 469  17148 File child = new File(f, DATA);
 470  17148 if (!child.exists())
 471    {
 472  1276 if (config.isCheckCharacterPortability())
 473    {
 474    /* Check whether the entire file path (root + fqn + data file name), is length portable */
 475  1276 isLengthPortablePath(child.getAbsolutePath());
 476    /* Check whether the fqn tree we're trying to store could contain non portable characters */
 477  1276 isCharacterPortableTree(fqn);
 478    }
 479   
 480  1276 if (!child.createNewFile())
 481    {
 482  0 throw new IOException("Unable to create file: " + child);
 483    }
 484    }
 485   
 486  17148 marshall(attrs, child);
 487    }
 488   
 489  211 protected boolean isCharacterPortableLocation(String fileAbsolutePath)
 490    {
 491  211 Matcher matcher = PATH_PATTERN.matcher(fileAbsolutePath);
 492  211 if (matcher.find())
 493    {
 494  5 log.warn("Cache loader location ( " + fileAbsolutePath + " ) contains one of these characters: '*' '<' '>' '|' '\"' '?'");
 495  5 log.warn("Directories containing these characters are illegal in some operative systems and could lead to portability issues");
 496  5 return false;
 497    }
 498   
 499  206 return true;
 500    }
 501   
 502  1286 protected boolean isCharacterPortableTree(Fqn fqn)
 503    {
 504  1286 List elements = fqn.peekElements();
 505    // Don't assume the Fqn is composed of Strings!!
 506  1286 for (Object anElement : elements)
 507    {
 508    // getFullPath converts Object to String via toString(), so we do too
 509  4123 Matcher matcher = FQN_PATTERN.matcher(anElement.toString());
 510  4123 if (matcher.find())
 511    {
 512  11 log.warn("One of the Fqn ( " + fqn + " ) elements contains one of these characters: '*' '<' '>' '|' '\"' '?' '\\' '/' ':' ");
 513  11 log.warn("Directories containing these characters are illegal in some operating systems and could lead to portability issues");
 514  11 return false;
 515    }
 516    }
 517   
 518  1275 return true;
 519    }
 520   
 521  1279 protected boolean isLengthPortablePath(String absoluteFqnPath)
 522    {
 523  1279 if (absoluteFqnPath.length() > 255)
 524    {
 525  1 log.warn("The full absolute path to the fqn that you are trying to store is bigger than 255 characters, this could lead to problems in Windows systems: " + absoluteFqnPath);
 526  1 return false;
 527    }
 528   
 529  1278 return true;
 530    }
 531   
 532  20504 protected Object unmarshall(File from) throws Exception
 533    {
 534  20504 FileInputStream fileIn = new FileInputStream(from);
 535  20504 ObjectInputStream input = ObjectSerializationFactory.createObjectInputStream(fileIn);
 536  20504 Object unmarshalledObj = getMarshaller().objectFromObjectStream(input);
 537  20504 input.close();
 538  20504 return unmarshalledObj;
 539    }
 540   
 541  17137 protected void marshall(Object obj, File to) throws Exception
 542    {
 543  17137 FileOutputStream fileOut = new FileOutputStream(to);
 544  17137 ObjectOutputStream output = ObjectSerializationFactory.createObjectOutputStream(fileOut);
 545  17137 getMarshaller().objectToObjectStream(obj, output);
 546  17137 output.close();
 547    }
 548    }