Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 555   Methods: 19
NCLOC: 382   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
CacheLoaderInterceptor.java 82.2% 85.4% 89.5% 84.7%
coverage coverage
 1    package org.jboss.cache.interceptors;
 2   
 3    import org.jboss.cache.CacheException;
 4    import org.jboss.cache.CacheSPI;
 5    import org.jboss.cache.Fqn;
 6    import org.jboss.cache.InvocationContext;
 7    import org.jboss.cache.NodeSPI;
 8    import org.jboss.cache.loader.CacheLoader;
 9    import org.jboss.cache.lock.NodeLock;
 10    import org.jboss.cache.marshall.MethodCall;
 11    import org.jboss.cache.marshall.MethodCallFactory;
 12    import org.jboss.cache.marshall.MethodDeclarations;
 13    import org.jboss.cache.transaction.GlobalTransaction;
 14    import org.jboss.cache.transaction.TransactionEntry;
 15    import org.jboss.cache.transaction.TransactionTable;
 16   
 17    import java.util.Collections;
 18    import java.util.HashMap;
 19    import java.util.List;
 20    import java.util.ListIterator;
 21    import java.util.Map;
 22    import java.util.Set;
 23   
 24    /**
 25    * Loads nodes that don't exist at the time of the call into memory from the CacheLoader
 26    *
 27    * @author Bela Ban
 28    * @version $Id: CacheLoaderInterceptor.java,v 1.85 2007/06/29 17:40:50 msurtani Exp $
 29    */
 30    public class CacheLoaderInterceptor extends Interceptor implements CacheLoaderInterceptorMBean
 31    {
 32    private long m_cacheLoads = 0;
 33    private long m_cacheMisses = 0;
 34    private TransactionTable txTable = null;
 35    protected boolean isActivation = false;
 36    protected CacheLoader loader;
 37   
 38    /**
 39    * True if CacheStoreInterceptor is in place.
 40    * This allows us to skip loading keys for remove(Fqn, key) and put(Fqn, key).
 41    * It also affects removal of node data and listing children.
 42    */
 43    protected boolean useCacheStore = true;
 44   
 45  2072 public void setCache(CacheSPI cache)
 46    {
 47  2072 super.setCache(cache);
 48  2072 txTable = cache.getTransactionTable();
 49  2072 this.loader = cache.getCacheLoaderManager().getCacheLoader();
 50    }
 51   
 52    /**
 53    * Makes sure a node is loaded into memory before a call executes (no-op if node is already loaded). If attributes
 54    * of a node are to be accessed by the method, the attributes are also loaded.
 55    *
 56    * @return
 57    * @throws Throwable
 58    */
 59  794745 public Object invoke(InvocationContext ctx) throws Throwable
 60    {
 61  794745 MethodCall m = ctx.getMethodCall();
 62  794745 Fqn fqn = null, fqn2 = null;// if set, load the data. fqn2 for 2nd fqn in move().
 63   
 64  794745 Object[] args = m.getArgs();
 65  794745 boolean acquireLock = false;// do we need to acquire a lock if we load this node from cloader?
 66   
 67  794745 boolean initNode = false;// keep uninitialized
 68  794745 Object key = null;
 69  794745 TransactionEntry entry = null;
 70  794745 GlobalTransaction gtx;
 71  794745 boolean recursive = false;// do we also load children?
 72   
 73   
 74  ? if ((gtx = ctx.getGlobalTransaction()) != null)
 75    {
 76  83145 entry = txTable.get(gtx);
 77    }
 78   
 79  794745 if (log.isTraceEnabled())
 80    {
 81  0 log.trace("invoke " + m);
 82    }
 83  794745 switch (m.getMethodId())
 84    {
 85  0 case MethodDeclarations.putDataEraseMethodLocal_id:
 86  960 case MethodDeclarations.putDataMethodLocal_id:
 87  960 fqn = (Fqn) args[1];
 88  960 initNode = true;
 89  960 break;
 90  0 case MethodDeclarations.putForExternalReadMethodLocal_id:
 91  157871 case MethodDeclarations.putKeyValMethodLocal_id:
 92  157871 fqn = (Fqn) args[1];
 93  157871 if (useCacheStore)
 94    {
 95  122242 initNode = true;
 96    }
 97    else
 98    {
 99  35629 acquireLock = true;
 100    }
 101  157871 break;
 102  36 case MethodDeclarations.moveMethodLocal_id:
 103  36 fqn = (Fqn) args[0];
 104  36 fqn2 = (Fqn) args[1];
 105  36 acquireLock = true;
 106    //initNode = true;
 107  36 recursive = true;
 108  36 break;
 109  0 case MethodDeclarations.addChildMethodLocal_id:
 110  0 fqn = (Fqn) args[1];
 111  0 break;
 112  277390 case MethodDeclarations.getKeyValueMethodLocal_id:
 113  277390 fqn = (Fqn) args[0];
 114  277390 key = args[1];
 115  277390 acquireLock = true;
 116  277390 break;
 117  282464 case MethodDeclarations.getNodeMethodLocal_id:
 118  297 case MethodDeclarations.getKeysMethodLocal_id:
 119  338 case MethodDeclarations.getChildrenNamesMethodLocal_id:
 120  0 case MethodDeclarations.releaseAllLocksMethodLocal_id:
 121  14 case MethodDeclarations.printMethodLocal_id:
 122  283113 fqn = (Fqn) args[0];
 123  283113 acquireLock = true;
 124  283113 break;
 125  49 case MethodDeclarations.rollbackMethod_id:
 126    // clean up nodesCreated map
 127  49 cleanupNodesCreated(entry);
 128  49 break;
 129  75326 default:
 130  75326 if (!useCacheStore)
 131    {
 132  2499 if (m.getMethodId() == MethodDeclarations.removeKeyMethodLocal_id)
 133    {
 134  53 fqn = (Fqn) args[1];
 135    }
 136  2446 else if (m.getMethodId() == MethodDeclarations.removeDataMethodLocal_id)
 137    {
 138  39 fqn = (Fqn) args[1];
 139  39 initNode = true;
 140    }
 141    }
 142  75326 break;
 143    }
 144   
 145    /* On the way in: load elements into cache from the CacheLoader if not yet in the cache. We need to synchronize
 146    this so only 1 thread attempts to load a given element */
 147   
 148  794745 if (fqn != null)
 149    {
 150  719448 if (fqn2 != null)
 151    {
 152  36 loadIfNeeded(ctx, fqn2, key, initNode, acquireLock, m, entry, false, m.getMethodId() == MethodDeclarations.moveMethodLocal_id);
 153    }
 154  719448 loadIfNeeded(ctx, fqn, key, initNode, acquireLock, m, entry, recursive, m.getMethodId() == MethodDeclarations.moveMethodLocal_id);
 155    }
 156   
 157  794745 return super.invoke(ctx);
 158    }
 159   
 160   
 161  719484 private void loadIfNeeded(InvocationContext ctx, Fqn fqn, Object key, boolean initNode, boolean acquireLock, MethodCall m, TransactionEntry entry, boolean recursive, boolean isMove) throws Throwable
 162    {
 163  719484 NodeSPI n = cache.peek(fqn, true);
 164   
 165  719484 boolean mustLoad = mustLoad(n, key);
 166  719484 if (log.isTraceEnabled())
 167    {
 168  0 log.trace("load element " + fqn + " mustLoad=" + mustLoad);
 169    }
 170  719484 if (mustLoad)
 171    {
 172  126343 if (initNode)
 173    {
 174  122997 n = createTempNode(fqn, entry);
 175    }
 176   
 177    // Only attempt to acquire this lock if we need to - i.e., if
 178    // the lock hasn't already been acquired by the Lock
 179    // interceptor. CRUD methods (put, remove) would have acquired
 180    // this lock - even if the node is not in memory and needs to be
 181    // loaded. Non-CRUD methods (put) would NOT have acquired this
 182    // lock so if we are to load the node from cache loader, we need
 183    // to acquire a write lock here. as a 'catch-all', DO NOT
 184    // attempt to acquire a lock here *anyway*, even for CRUD
 185    // methods - this leads to a deadlock when you have threads
 186    // simultaneously trying to create a node. See
 187    // org.jboss.cache.loader.deadlock.ConcurrentCreationDeadlockTest
 188    // - Manik Surtani (21 March 2006)
 189  126343 if (acquireLock)
 190    {
 191  3331 lock(fqn, NodeLock.LockType.WRITE, false);// non-recursive for now
 192    }
 193   
 194  126343 if (!initNode && !wasRemovedInTx(fqn, ctx.getGlobalTransaction()))
 195    {
 196  3346 n = loadNode(ctx, fqn, n, entry);
 197    }
 198    }
 199   
 200    // The complete list of children aren't known without loading them
 201  719484 if (recursive || m.getMethodId() == MethodDeclarations.getChildrenNamesMethodLocal_id)
 202    {
 203  360 loadChildren(fqn, n, recursive, isMove);
 204    }
 205    }
 206   
 207    /**
 208    * Load the children.
 209    *
 210    * @param node may be null if the node was not found.
 211    */
 212  386 private void loadChildren(Fqn fqn, NodeSPI node, boolean recursive, boolean isMove) throws Throwable
 213    {
 214   
 215  386 if (node != null && node.getChildrenLoaded())
 216    {
 217  34 return;
 218    }
 219  352 Set children_names = loader.getChildrenNames(fqn);
 220   
 221  352 if (log.isTraceEnabled())
 222    {
 223  0 log.trace("load children " + fqn + " children=" + children_names);
 224    }
 225   
 226    // For getChildrenNames null means no children
 227  352 if (children_names == null)
 228    {
 229  116 if (node != null)
 230    {
 231  102 if (useCacheStore)
 232    {
 233  34 node.removeChildrenDirect();//getChildrenMapDirect().clear();
 234    }
 235  102 node.setChildrenLoaded(true);
 236    }
 237  116 return;
 238    }
 239   
 240    // Create if node had not been created already
 241  236 if (node == null)
 242    {
 243  0 node = createNodes(fqn, null);// dont care about local transactions
 244    }
 245   
 246    // Create one DataNode per child, mark as UNINITIALIZED
 247  236 for (Object name : children_names)
 248    {
 249  598 Fqn child_fqn = new Fqn(name);// this is a RELATIVE Fqn!!
 250   
 251    // create child if it didn't exist
 252  598 NodeSPI child = node.addChildDirect(child_fqn);
 253  598 if ((isMove || isActivation) && recursive)
 254    {
 255    // load data for children as well!
 256  26 child.putAllDirect(loader.get(child.getFqn()));
 257  26 child.setDataLoaded(true);
 258    }
 259    else
 260    {
 261  572 child.setDataLoaded(false);
 262    }
 263  598 if (recursive)
 264    {
 265  26 loadChildren(child.getFqn(), child, true, isMove);
 266    }
 267    }
 268  236 lock(fqn, recursive ? NodeLock.LockType.WRITE : NodeLock.LockType.READ, true);// recursive=true: lock entire subtree
 269  236 node.setChildrenLoaded(true);
 270    }
 271   
 272  719484 private boolean mustLoad(NodeSPI n, Object key)
 273    {
 274  719484 if (n == null)
 275    {
 276  2119 log.trace("mustLoad, node null");
 277  2119 return true;
 278    }
 279  717365 if (!n.getDataLoaded())
 280    {
 281  124224 log.trace("must Load, uninitialized");
 282  124224 return true;
 283    }
 284    /*
 285    if (!n.getKeys().contains(key)) {
 286    log.trace("must Load, no key");
 287    return true;
 288    }
 289    */
 290  593141 return false;
 291    }
 292   
 293  22 public long getCacheLoaderLoads()
 294    {
 295  22 return m_cacheLoads;
 296    }
 297   
 298  23 public long getCacheLoaderMisses()
 299    {
 300  23 return m_cacheMisses;
 301    }
 302   
 303  2 public void resetStatistics()
 304    {
 305  2 m_cacheLoads = 0;
 306  2 m_cacheMisses = 0;
 307    }
 308   
 309  0 public Map<String, Object> dumpStatistics()
 310    {
 311  0 Map<String, Object> retval = new HashMap<String, Object>();
 312  0 retval.put("CacheLoaderLoads", m_cacheLoads);
 313  0 retval.put("CacheLoaderMisses", m_cacheMisses);
 314  0 return retval;
 315    }
 316   
 317  3567 protected void lock(Fqn fqn, NodeLock.LockType lock_type, boolean recursive) throws Throwable
 318    {
 319   
 320  112 if (configuration.isNodeLockingOptimistic()) return;
 321   
 322  3455 MethodCall m = MethodCallFactory.create(MethodDeclarations.lockMethodLocal,
 323    fqn, lock_type, recursive);
 324   
 325    // hacky
 326  3455 cache.getInterceptorChain().get(0).invoke(InvocationContext.fromMethodCall(m));
 327    // super.invoke(m);
 328    }
 329   
 330    /**
 331    * Retrieves a node from memory; doesn't access the cache loader
 332    *
 333    * @param fqn
 334    */
 335  1559 protected NodeSPI getNode(Fqn fqn)
 336    {
 337  1559 return cache.peek(fqn, true);
 338    // int treeNodeSize = fqn.size();
 339    //
 340    // // root node
 341    // Node n = cache.getRoot();
 342    // Node child_node;
 343    // Object child_name;
 344    // for (int i = 0; i < treeNodeSize && n != null; i++)
 345    // {
 346    // child_name = fqn.get(i);
 347    // cache.getInvocationContext().getOptionOverrides().setBypassInterceptorChain(true);
 348    // child_node = n.getChild(new Fqn(child_name));
 349    // n = child_node;
 350    // }
 351    // return n;
 352    }
 353   
 354    /**
 355    * Returns true if the FQN or parent was removed during the current
 356    * transaction.
 357    * This is O(N) WRT to the number of modifications so far.
 358    */
 359  3346 private boolean wasRemovedInTx(Fqn fqn, GlobalTransaction t)
 360    {
 361  3346 if (t == null)
 362    {
 363  2847 return false;
 364    }
 365  499 TransactionEntry entry = txTable.get(t);
 366  499 for (MethodCall m : entry.getCacheLoaderModifications())
 367    {
 368  3226 if (m.getMethodId() == MethodDeclarations.removeNodeMethodLocal_id
 369    && fqn.isChildOrEquals((Fqn) m.getArgs()[1]))
 370    {
 371  0 return true;
 372    }
 373    }
 374  499 return false;
 375    }
 376   
 377    /**
 378    * Loads a node from disk; if it exists creates parent TreeNodes.
 379    * If it doesn't exist on disk but in memory, clears the
 380    * uninitialized flag, otherwise returns null.
 381    */
 382  3346 private NodeSPI loadNode(InvocationContext ctx, Fqn fqn, NodeSPI n, TransactionEntry entry) throws Exception
 383    {
 384  0 if (log.isTraceEnabled()) log.trace("loadNode " + fqn);
 385  3346 Map nodeData = loadData(fqn);
 386  3346 if (nodeData != null)
 387    {
 388  2224 log.trace("Node data is not null, loading");
 389   
 390  2224 cache.getNotifier().notifyNodeLoaded(fqn, true, Collections.emptyMap(), ctx);
 391  2224 if (isActivation)
 392    {
 393  1473 cache.getNotifier().notifyNodeActivated(fqn, true, Collections.emptyMap(), ctx);
 394    }
 395   
 396  2224 n = createNodes(fqn, entry);
 397    // n.clearDataDirect();
 398  2224 n.putAllDirect(nodeData);
 399   
 400  2224 cache.getNotifier().notifyNodeLoaded(fqn, false, nodeData, ctx);
 401  2224 if (isActivation)
 402    {
 403  1473 cache.getNotifier().notifyNodeActivated(fqn, false, nodeData, ctx);
 404    }
 405    }
 406   
 407  3346 if (n != null && !n.getDataLoaded())
 408    {
 409  1272 log.trace("Setting dataLoaded to true");
 410  1272 n.setDataLoaded(true);
 411    }
 412  3346 return n;
 413    }
 414   
 415    /**
 416    * Creates a new memory node in preparation for storage.
 417    */
 418  122997 private NodeSPI createTempNode(Fqn fqn, TransactionEntry entry) throws Exception
 419    {
 420  122997 NodeSPI n = createNodes(fqn, entry);
 421  122997 n.setDataLoaded(false);
 422  122997 if (log.isTraceEnabled())
 423    {
 424  0 log.trace("createTempNode n " + n);
 425    }
 426  122997 return n;
 427    }
 428   
 429   
 430  125221 private NodeSPI createNodes(Fqn fqn, TransactionEntry entry) throws Exception
 431    {
 432  125215 Fqn tmp_fqn = Fqn.ROOT;
 433   
 434  125221 int size = fqn.size();
 435   
 436    // root node
 437  125221 NodeSPI n = cache.getRoot();
 438  125221 for (int i = 0; i < size; i++)
 439    {
 440  383779 Object child_name = fqn.get(i);
 441  383779 tmp_fqn = new Fqn(tmp_fqn, child_name);
 442   
 443  383779 NodeSPI child_node = findChild(n, child_name);
 444  383779 boolean last = (i == size - 1);
 445   
 446  383779 if (child_node == null)
 447    {
 448  1804 if (last)
 449    {
 450  1715 child_node = n.addChildDirect(new Fqn(child_name));
 451  1715 child_node.setDataLoaded(true);
 452    }
 453    else
 454    {
 455  89 child_node = n.addChildDirect(new Fqn(child_name));
 456  89 child_node.setDataLoaded(false);
 457    }
 458   
 459  1804 if (entry != null)
 460    {
 461  110 entry.loadUninitialisedNode(tmp_fqn);
 462    }
 463    }
 464   
 465  383779 n = child_node;
 466    }
 467   
 468  125221 return n;
 469    }
 470   
 471  383779 private NodeSPI findChild(NodeSPI node, Object child_name)
 472    {
 473  383779 Map children = node.getChildrenMapDirect();
 474  188 if (children == null) return null;
 475  383591 return (NodeSPI) children.get(child_name);
 476    }
 477   
 478  49 private void cleanupNodesCreated(TransactionEntry entry)
 479    {
 480  49 boolean traceEnabled = log.isTraceEnabled();
 481  49 log.trace("Removing temporarily created nodes from treecache");
 482   
 483    // this needs to be done in reverse order.
 484  49 List list = entry.getDummyNodesCreatedByCacheLoader();
 485  49 if (list != null && list.size() > 0)
 486    {
 487  0 ListIterator i = list.listIterator(list.size());
 488  0 while (i.hasPrevious())
 489    {
 490  0 Fqn fqn = (Fqn) i.previous();
 491  0 try
 492    {
 493  0 cache.evict(fqn, false);
 494    }
 495    catch (CacheException e)
 496    {
 497  0 if (traceEnabled) log.trace("Unable to evict node " + fqn, e);
 498    }
 499    }
 500    }
 501    }
 502   
 503   
 504  3346 private Map loadData(Fqn fqn) throws Exception
 505    {
 506   
 507  3346 Map nodeData = loader.get(fqn);
 508  3346 boolean nodeExists = (nodeData != null);
 509  0 if (log.isTraceEnabled()) log.trace("nodeExists " + nodeExists);
 510   
 511  3346 if (configuration.getExposeManagementStatistics() && getStatisticsEnabled())
 512    {
 513  3346 if (nodeExists)
 514    {
 515  2224 m_cacheLoads++;
 516    }
 517    else
 518    {
 519  1122 m_cacheMisses++;
 520    }
 521    }
 522   
 523    // BES Jan-4-2007 Stop doing this; it's annoying and people
 524    // should have converted by now
 525    // if (!nodeExists && isCustomCacheLoader)
 526    // {
 527    // warnCustom();
 528    // }
 529   
 530    // BES Jan-21-2007 Do the notifications in loadNode, before and after
 531    // we create nodes
 532    // if (nodeExists)
 533    // {
 534    // cache.getNotifier().notifyNodeLoaded(fqn, true, Collections.emptyMap(), true);
 535    // cache.getNotifier().notifyNodeLoaded(fqn, false, nodeData, true);
 536    //
 537    // if (isActivation)
 538    // {
 539    // cache.getNotifier().notifyNodeActivated(fqn, true, true);
 540    // cache.getNotifier().notifyNodeActivated(fqn, false, true);
 541    // }
 542    // }
 543   
 544  3346 return nodeData;
 545    }
 546   
 547  0 private void warnCustom()
 548    {
 549  0 log.warn("CacheLoader.get(Fqn) returned a null; assuming the node nodes not exist.");
 550  0 log.warn("The CacheLoader interface has changed since JBossCache 1.3.x");
 551  0 log.warn("Please see http://jira.jboss.com/jira/browse/JBCACHE-118");
 552  0 log.warn("CacheLoader.get() should return an empty Map if the node does exist but doesn't have any attributes.");
 553    }
 554   
 555    }