Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 716   Methods: 40
NCLOC: 486   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
JdbmCacheLoader.java 72.6% 82.4% 92.5% 81%
coverage coverage
 1    package org.jboss.cache.loader.jdbm;
 2   
 3    import jdbm.RecordManager;
 4    import jdbm.RecordManagerFactory;
 5    import jdbm.btree.BTree;
 6    import jdbm.helper.Tuple;
 7    import jdbm.helper.TupleBrowser;
 8    import net.jcip.annotations.ThreadSafe;
 9    import org.apache.commons.logging.Log;
 10    import org.apache.commons.logging.LogFactory;
 11    import org.jboss.cache.CacheSPI;
 12    import org.jboss.cache.Fqn;
 13    import org.jboss.cache.FqnComparator;
 14    import org.jboss.cache.Modification;
 15    import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
 16    import org.jboss.cache.loader.AbstractCacheLoader;
 17   
 18    import java.io.File;
 19    import java.io.IOException;
 20    import java.io.Serializable;
 21    import java.util.Collections;
 22    import java.util.HashMap;
 23    import java.util.HashSet;
 24    import java.util.List;
 25    import java.util.Map;
 26    import java.util.Properties;
 27    import java.util.Set;
 28    import java.util.concurrent.ConcurrentHashMap;
 29   
 30    /**
 31    * A persistent <code>CacheLoader</code> based on the JDBM project.
 32    * See http://jdbm.sourceforge.net/ .
 33    * Does not support transaction isolation.
 34    * <p/>
 35    * <p>The configuration string format is:</p>
 36    * <pre>environmentDirectoryName[#databaseName]</pre>
 37    * <p>where databaseName, if omitted, defaults to the ClusterName property
 38    * of the CacheImpl.</p>
 39    * <p/>
 40    * Data is sorted out like:
 41    * <pre>
 42    * / = N
 43    * /node1 = N
 44    * /node1/K/k1 = v1
 45    * /node1/K/k2 = v2
 46    * /node2 = N
 47    * /node2/node3 = N
 48    * /node2/node3/K/k1 = v1
 49    * /node2/node3/K/k2 = v2
 50    * /node2/node4 = N
 51    * </pre>
 52    * N represents a node, K represents a key block. k and v represent key/value
 53    * pairs.
 54    * <p/>
 55    * TODO the browse operations lock the entire tree; eventually the JDBM team
 56    * plans to fix this.
 57    *
 58    * @author Elias Ross
 59    * @version $Id: JdbmCacheLoader.java,v 1.28 2007/06/19 17:46:50 msurtani Exp $
 60    */
 61    @ThreadSafe
 62    public class JdbmCacheLoader extends AbstractCacheLoader
 63    {
 64    private static final Log log = LogFactory.getLog(JdbmCacheLoader.class);
 65   
 66    private static final String KEYS = "K";
 67    private static final String NODE = "N";
 68    private static final String NAME = "JdbmCacheLoader";
 69   
 70    private JdbmCacheLoaderConfig config;
 71    private String cacheDbName;
 72    private RecordManager recman;
 73    private BTree tree;
 74    private Map<Object, List<Modification>> transactions = new ConcurrentHashMap<Object, List<Modification>>();
 75   
 76    /*
 77    * Service implementation -- lifecycle methods.
 78    * Note that setConfig() and setCache() are called before create().
 79    */
 80   
 81  67 public void create() throws Exception
 82    {
 83  67 checkNotOpen();
 84    }
 85   
 86  67 public void destroy()
 87    {
 88    }
 89   
 90    /**
 91    * Opens the environment and the database specified by the configuration
 92    * string. The environment and databases are created if necessary.
 93    */
 94  67 public void start()
 95    throws Exception
 96    {
 97   
 98  67 log.trace("Starting JdbmCacheLoader instance.");
 99  67 checkNotOpen();
 100  67 checkNonNull(cache, "CacheSPI object is required");
 101   
 102  67 String locationStr = config.getLocation();
 103  67 if (locationStr == null)
 104    {
 105  0 locationStr = System.getProperty("java.io.tmpdir");
 106  0 config.setLocation(locationStr);
 107    }
 108   
 109    // test location
 110  67 File location = new File(locationStr);
 111  67 if (!location.exists())
 112    {
 113  61 boolean created = location.mkdirs();
 114  0 if (!created) throw new IOException("Unable to create cache loader location " + location);
 115   
 116    }
 117  67 if (!location.isDirectory())
 118    {
 119  0 throw new IOException("Cache loader location [" + location + "] is not a directory!");
 120    }
 121   
 122    /* Parse config string. */
 123  67 File homeDir;
 124  67 int offset = locationStr.indexOf('#');
 125  67 if (offset >= 0 && offset < locationStr.length() - 1)
 126    {
 127  0 homeDir = new File(locationStr.substring(0, offset));
 128  0 cacheDbName = locationStr.substring(offset + 1);
 129    }
 130    else
 131    {
 132  67 homeDir = new File(locationStr);
 133  67 cacheDbName = cache.getClusterName();
 134    }
 135   
 136  67 try
 137    {
 138  67 openDatabase(new File(homeDir, cacheDbName));
 139    }
 140    catch (Exception e)
 141    {
 142  0 destroy();
 143  0 throw e;
 144    }
 145    }
 146   
 147    /**
 148    * Opens all databases and initializes database related information.
 149    */
 150  67 private void openDatabase(File f)
 151    throws Exception
 152    {
 153  67 Properties props = new Properties();
 154    // Incorporate properties from setConfig() ?
 155    // props.put(RecordManagerOptions.SERIALIZER, RecordManagerOptions.SERIALIZER_EXTENSIBLE);
 156    // props.put(RecordManagerOptions.PROFILE_SERIALIZATION, "false");
 157  67 recman = RecordManagerFactory.createRecordManager(f.toString(), props);
 158  67 long recid = recman.getNamedObject(NAME);
 159  67 log.debug(NAME + " located as " + recid);
 160  67 if (recid == 0)
 161    {
 162  67 tree = BTree.createInstance(recman, new JdbmFqnComparator());
 163  67 recman.setNamedObject(NAME, tree.getRecid());
 164    }
 165    else
 166    {
 167  0 tree = BTree.load(recman, recid);
 168    }
 169   
 170  67 log.info("JDBM database " + f + " opened with " + tree.size() + " entries");
 171    }
 172   
 173    /**
 174    * Closes all databases, ignoring exceptions, and nulls references to all
 175    * database related information.
 176    */
 177  67 private void closeDatabases()
 178    {
 179  67 if (recman != null)
 180    {
 181  67 try
 182    {
 183  67 recman.close();
 184    }
 185    catch (Exception shouldNotOccur)
 186    {
 187  0 log.warn("Caught unexpected exception", shouldNotOccur);
 188    }
 189    }
 190  67 recman = null;
 191  67 tree = null;
 192    }
 193   
 194    /**
 195    * Closes the databases and environment, and nulls references to them.
 196    */
 197  67 public void stop()
 198    {
 199  67 log.debug("stop");
 200  67 closeDatabases();
 201    }
 202   
 203    /*
 204    * CacheLoader implementation.
 205    */
 206   
 207    /**
 208    * Sets the configuration string for this cache loader.
 209    */
 210  67 public void setConfig(IndividualCacheLoaderConfig base)
 211    {
 212  67 checkNotOpen();
 213   
 214  67 if (base instanceof JdbmCacheLoaderConfig)
 215    {
 216  0 this.config = (JdbmCacheLoaderConfig) base;
 217    }
 218    else
 219    {
 220  67 config = new JdbmCacheLoaderConfig(base);
 221    }
 222   
 223  0 if (log.isTraceEnabled()) log.trace("Configuring cache loader with location = " + config.getLocation());
 224    }
 225   
 226  67 public IndividualCacheLoaderConfig getConfig()
 227    {
 228  67 return config;
 229    }
 230   
 231    /**
 232    * Sets the CacheImpl owner of this cache loader.
 233    */
 234  67 public void setCache(CacheSPI c)
 235    {
 236  67 super.setCache(c);
 237  67 checkNotOpen();
 238    }
 239   
 240    /**
 241    * Returns a special FQN for keys of a node.
 242    */
 243  1744 private Fqn keys(Fqn name)
 244    {
 245  1744 return new Fqn(name, KEYS);
 246    }
 247   
 248    /**
 249    * Returns a special FQN for key of a node.
 250    */
 251  4268 private Fqn key(Fqn name, Object key)
 252    {
 253  4268 return new Fqn(name, KEYS, nullMask(key));
 254    }
 255   
 256    /**
 257    * Returns an unmodifiable set of relative children names, or
 258    * returns null if the parent node is not found or if no children are found.
 259    * This is a fairly expensive operation, and is assumed to be performed by
 260    * browser applications. Calling this method as part of a run-time
 261    * transaction is not recommended.
 262    */
 263  2065 public Set<String> getChildrenNames(Fqn name)
 264    throws Exception
 265    {
 266   
 267  2065 if (log.isTraceEnabled())
 268    {
 269  0 log.trace("getChildrenNames " + name);
 270    }
 271   
 272  2065 synchronized (tree)
 273    {
 274  2065 return getChildrenNames0(name);
 275    }
 276    }
 277   
 278  2065 private Set<String> getChildrenNames0(Fqn name) throws IOException
 279    {
 280  2065 TupleBrowser browser = tree.browse(name);
 281  2065 Tuple t = new Tuple();
 282   
 283  2065 if (browser.getNext(t))
 284    {
 285  2028 if (!t.getValue().equals(NODE))
 286    {
 287  11 log.trace(" not a node");
 288  11 return null;
 289    }
 290    }
 291    else
 292    {
 293  37 log.trace(" no nodes");
 294  37 return null;
 295    }
 296   
 297  2017 Set<String> set = new HashSet<String>();
 298   
 299    // Want only /a/b/c/X nodes
 300  2017 int depth = name.size() + 1;
 301  2017 while (browser.getNext(t))
 302    {
 303  1581 Fqn fqn = (Fqn) t.getKey();
 304  1581 int size = fqn.size();
 305  1581 if (size < depth)
 306    {
 307  948 break;
 308    }
 309  633 if (size == depth && t.getValue().equals(NODE))
 310    {
 311  92 set.add((String) fqn.getLastElement());
 312    }
 313    }
 314   
 315  2017 if (set.isEmpty())
 316    {
 317  1977 return null;
 318    }
 319   
 320  40 return Collections.unmodifiableSet(set);
 321    }
 322   
 323    /**
 324    * Returns a map containing all key-value pairs for the given FQN, or null
 325    * if the node is not present.
 326    * This operation is always non-transactional, even in a transactional
 327    * environment.
 328    */
 329  2348 public Map get(Fqn name)
 330    throws Exception
 331    {
 332   
 333  2348 checkOpen();
 334  2348 checkNonNull(name, "name");
 335   
 336  2348 if (tree.find(name) == null)
 337    {
 338  604 if (log.isTraceEnabled())
 339    {
 340  0 log.trace("get, no node: " + name);
 341    }
 342  604 return null;
 343    }
 344   
 345  1744 Fqn keys = keys(name);
 346  1744 Tuple t = new Tuple();
 347  1744 Map map = new HashMap();
 348   
 349  1744 synchronized (tree)
 350    {
 351  1744 TupleBrowser browser = tree.browse(keys);
 352  1744 while (browser.getNext(t))
 353    {
 354  2528 Fqn fqn = (Fqn) t.getKey();
 355  2528 if (!fqn.isChildOf(keys))
 356    {
 357  1040 break;
 358    }
 359  1488 Object k = fqn.getLastElement();
 360  1488 Object v = t.getValue();
 361  1488 map.put(nullUnmask(k), nullUnmask(v));
 362    }
 363    }
 364   
 365  1744 if (log.isTraceEnabled())
 366    {
 367  0 log.trace("get " + name + " map=" + map);
 368    }
 369   
 370  1744 return map;
 371    }
 372   
 373    /**
 374    * Returns whether the given node exists.
 375    */
 376  4330 public boolean exists(Fqn name) throws IOException
 377    {
 378  4330 return tree.find(name) != null;
 379    }
 380   
 381  8511 private void commit() throws Exception
 382    {
 383  8511 recman.commit();
 384    }
 385   
 386    /**
 387    * Stores a single FQN-key-value record.
 388    * Intended to be used in a non-transactional environment, but will use
 389    * auto-commit in a transactional environment.
 390    */
 391  2162 public Object put(Fqn name, Object key, Object value) throws Exception
 392    {
 393  2162 try
 394    {
 395  2162 return put0(name, key, value);
 396    }
 397    finally
 398    {
 399  2162 commit();
 400    }
 401    }
 402   
 403  2162 private Object put0(Fqn name, Object key, Object value) throws Exception
 404    {
 405  2162 checkNonNull(name, "name");
 406  2162 makeNode(name);
 407  2162 Fqn rec = key(name, key);
 408  2162 Object oldValue = insert(rec, value);
 409  2162 if (log.isTraceEnabled())
 410    {
 411  0 log.trace("put " + rec + " value=" + value + " old=" + oldValue);
 412    }
 413  2162 return oldValue;
 414    }
 415   
 416    /**
 417    * Stores a map of key-values for a given FQN, but does not delete existing
 418    * key-value pairs (that is, it does not erase).
 419    * Intended to be used in a non-transactional environment, but will use
 420    * auto-commit in a transactional environment.
 421    */
 422  2100 public void put(Fqn name, Map values) throws Exception
 423    {
 424  2100 put0(name, values);
 425  2100 commit();
 426    }
 427   
 428  2100 private void put0(Fqn name, Map<?, ?> values) throws Exception
 429    {
 430  2100 if (log.isTraceEnabled())
 431    {
 432  0 log.trace("put " + name + " values=" + values);
 433    }
 434  2100 makeNode(name);
 435  2100 if (values == null)
 436    {
 437  70 return;
 438    }
 439   
 440  2030 for (Map.Entry me : values.entrySet())
 441    {
 442  80 Fqn rec = key(name, me.getKey());
 443  80 insert(rec, nullMask(me.getValue()));
 444    }
 445    }
 446   
 447    /**
 448    * Marks a FQN as a node.
 449    */
 450  4262 private void makeNode(Fqn fqn) throws IOException
 451    {
 452  4262 if (exists(fqn))
 453    {
 454  3394 return;
 455    }
 456  868 int size = fqn.size();
 457    // TODO should not modify so darn often
 458  868 for (int i = size; i >= 0; i--)
 459    {
 460  1825 Fqn child = fqn.getAncestor(i);
 461  1825 Object existing = tree.insert(child, NODE, false);
 462  1825 if (existing != null)
 463    {
 464  803 break;
 465    }
 466    }
 467    }
 468   
 469  2242 private Object insert(Fqn fqn, Object value) throws IOException
 470    {
 471  2242 return nullUnmask(tree.insert(fqn, nullMask(value), true));
 472    }
 473   
 474    /**
 475    * Erase a FQN and children.
 476    * Does not commit.
 477    */
 478  2159 private void erase0(Fqn name)
 479    throws IOException
 480    {
 481  2159 erase0(name, true);
 482    }
 483   
 484  2174 private void erase0(Fqn name, boolean self)
 485    throws IOException
 486    {
 487  2174 if (log.isTraceEnabled())
 488    {
 489  0 log.trace("erase " + name + " self=" + self);
 490    }
 491  2174 synchronized (tree)
 492    {
 493  2174 TupleBrowser browser = tree.browse(name);
 494  2174 Tuple t = new Tuple();
 495  2174 if (browser.getNext(t))
 496    {
 497  1077 if (self)
 498    {
 499  1068 tree.remove(t.getKey());
 500    }
 501    }
 502  2174 while (browser.getNext(t))
 503    {
 504  1527 Fqn fqn = (Fqn) t.getKey();
 505  1527 if (!fqn.isChildOf(name))
 506    {
 507  898 break;
 508    }
 509  629 tree.remove(fqn);
 510    }
 511    }
 512    }
 513   
 514    /**
 515    * Erase a FQN's key.
 516    * Does not commit.
 517    */
 518  2026 private Object eraseKey0(Fqn name, Object key)
 519    throws IOException
 520    {
 521  2026 if (log.isTraceEnabled())
 522    {
 523  0 log.trace("eraseKey " + name + " key " + key);
 524    }
 525  2026 Fqn fqnKey = key(name, key);
 526  2026 try
 527    {
 528  2026 return tree.remove(fqnKey);
 529    }
 530    catch (IllegalArgumentException e)
 531    {
 532    // Seems to be harmless
 533    // log.warn("IllegalArgumentException for " + fqnKey);
 534    // dump();
 535  1922 return null;
 536    }
 537    }
 538   
 539    /**
 540    * Applies the given modifications.
 541    * Intended to be used in a non-transactional environment, but will use
 542    * auto-commit in a transactional environment.
 543    */
 544  61 public void put(List<Modification> modifications)
 545    throws Exception
 546    {
 547   
 548  61 checkOpen();
 549  61 checkNonNull(modifications, "modifications");
 550   
 551  61 super.put(modifications);
 552  61 commit();
 553    }
 554   
 555    /**
 556    * Deletes the node for a given FQN and all its descendent nodes.
 557    * Intended to be used in a non-transactional environment, but will use
 558    * auto-commit in a transactional environment.
 559    */
 560  2159 public void remove(Fqn name)
 561    throws Exception
 562    {
 563  2159 erase0(name);
 564  2159 commit();
 565    }
 566   
 567    /**
 568    * Deletes a single FQN-key-value record.
 569    * Intended to be used in a non-transactional environment, but will use
 570    * auto-commit in a transactional environment.
 571    */
 572  2026 public Object remove(Fqn name, Object key)
 573    throws Exception
 574    {
 575   
 576  2026 try
 577    {
 578  2026 return eraseKey0(name, key);
 579    }
 580    finally
 581    {
 582  2026 commit();
 583    }
 584    }
 585   
 586    /**
 587    * Clears the map for the given node, but does not remove the node.
 588    */
 589  15 public void removeData(Fqn name)
 590    throws Exception
 591    {
 592  15 erase0(name, false);
 593    }
 594   
 595    /**
 596    * Applies and commits the given modifications in one transaction.
 597    */
 598  6 public void prepare(Object tx, List<Modification> modifications, boolean onePhase)
 599    throws Exception
 600    {
 601  6 if (onePhase)
 602    {
 603  1 put(modifications);
 604    }
 605    else
 606    {
 607  5 transactions.put(tx, modifications);
 608    }
 609    }
 610   
 611    /**
 612    * Commits a transaction.
 613    */
 614  3 public void commit(Object tx) throws Exception
 615    {
 616  3 List<Modification> modifications = transactions.remove(tx);
 617  3 if (modifications == null)
 618    {
 619  0 throw new IllegalStateException("transaction " + tx + " not found in transaction table");
 620    }
 621  3 put(modifications);
 622  3 commit();
 623    }
 624   
 625    /**
 626    * Removes transaction in progress.
 627    */
 628  2 public void rollback(Object tx)
 629    {
 630  2 transactions.remove(tx);
 631    }
 632   
 633    /**
 634    * Throws an exception if the environment is not open.
 635    */
 636  2409 private void checkOpen()
 637    {
 638  2409 if (tree == null)
 639    {
 640  0 throw new IllegalStateException(
 641    "Operation not allowed before calling create()");
 642    }
 643    }
 644   
 645    /**
 646    * Throws an exception if the environment is not open.
 647    */
 648  268 private void checkNotOpen()
 649    {
 650  268 if (tree != null)
 651    {
 652  0 throw new IllegalStateException(
 653    "Operation not allowed after calling create()");
 654    }
 655    }
 656   
 657    /**
 658    * Throws an exception if the parameter is null.
 659    */
 660  4638 private void checkNonNull(Object param, String paramName)
 661    {
 662  4638 if (param == null)
 663    {
 664  0 throw new NullPointerException(
 665    "Parameter must not be null: " + paramName);
 666    }
 667    }
 668   
 669  6590 private Object nullMask(Object o)
 670    {
 671  6590 return (o == null) ? Null.NULL : o;
 672    }
 673   
 674  5218 private Object nullUnmask(Object o)
 675    {
 676  5218 return (o == Null.NULL) ? null : o;
 677    }
 678   
 679    /**
 680    * Dumps the tree to debug.
 681    */
 682  0 public void dump() throws IOException
 683    {
 684  0 dump(Fqn.ROOT);
 685    }
 686   
 687    /**
 688    * Dumps the tree past the key to debug.
 689    */
 690  0 public void dump(Object key) throws IOException
 691    {
 692  0 TupleBrowser browser = tree.browse(key);
 693  0 Tuple t = new Tuple();
 694  0 log.debug("contents: " + key);
 695  0 while (browser.getNext(t))
 696    {
 697  0 log.debug(t.getKey() + "\t" + t.getValue());
 698    }
 699  0 log.debug("");
 700    }
 701   
 702  0 public String toString()
 703    {
 704  0 BTree bt = tree;
 705  0 int size = (bt == null) ? -1 : bt.size();
 706  0 return "JdbmCacheLoader locationStr=" + config.getLocation() +
 707    " size=" + size;
 708    }
 709   
 710    }
 711   
 712    class JdbmFqnComparator extends FqnComparator implements Serializable
 713    {
 714    private static final long serialVersionUID = 1000;
 715    }
 716