Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 525   Methods: 17
NCLOC: 349   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
OptimisticNodeInterceptor.java 72.1% 89.4% 100% 85.5%
coverage coverage
 1    /*
 2    * JBoss, Home of Professional Open Source
 3    *
 4    * Distributable under LGPL license.
 5    * See terms of license at gnu.org.
 6    */
 7    package org.jboss.cache.interceptors;
 8   
 9    import org.jboss.cache.CacheException;
 10    import org.jboss.cache.CacheSPI;
 11    import org.jboss.cache.Fqn;
 12    import org.jboss.cache.InvocationContext;
 13    import org.jboss.cache.NodeFactory;
 14    import org.jboss.cache.NodeNotExistsException;
 15    import org.jboss.cache.NodeSPI;
 16    import org.jboss.cache.config.Option;
 17    import org.jboss.cache.marshall.MethodCall;
 18    import org.jboss.cache.marshall.MethodDeclarations;
 19    import org.jboss.cache.notifications.Notifier;
 20    import static org.jboss.cache.notifications.event.NodeModifiedEvent.ModificationType.*;
 21    import org.jboss.cache.optimistic.DataVersion;
 22    import org.jboss.cache.optimistic.DefaultDataVersion;
 23    import org.jboss.cache.optimistic.TransactionWorkspace;
 24    import org.jboss.cache.optimistic.WorkspaceNode;
 25    import org.jboss.cache.transaction.GlobalTransaction;
 26   
 27    import java.util.Collections;
 28    import java.util.HashMap;
 29    import java.util.Map;
 30    import java.util.SortedMap;
 31   
 32    /**
 33    * Operations on nodes are done on the copies that exist in the workspace rather than passed down
 34    * to the {@link org.jboss.cache.interceptors.CallInterceptor}. These operations happen in this interceptor.
 35    *
 36    * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 37    * @author <a href="mailto:stevew@jofti.com">Steve Woodcock (stevew@jofti.com)</a>
 38    */
 39    public class OptimisticNodeInterceptor extends OptimisticInterceptor
 40    {
 41    /**
 42    * Needed for the creation of workspace nodes based on underlying nodes in the cache.
 43    */
 44    private NodeFactory nodeFactory;
 45    private Notifier notifier;
 46   
 47  907 public void setCache(CacheSPI c)
 48    {
 49  907 super.setCache(c);
 50  907 nodeFactory = c.getConfiguration().getRuntimeConfig().getNodeFactory();
 51  907 notifier = cache.getNotifier();
 52    }
 53   
 54  1054615 public Object invoke(InvocationContext ctx) throws Throwable
 55    {
 56  1054615 MethodCall m = ctx.getMethodCall();
 57  1054615 Object[] args = m.getArgs();
 58   
 59  1054615 Object result = null;
 60   
 61  1054615 if (MethodDeclarations.isCrudMethod(m.getMethodId()))
 62    {
 63  1904 GlobalTransaction gtx = getGlobalTransaction(ctx);
 64  1904 TransactionWorkspace workspace = getTransactionWorkspace(gtx);
 65  1904 Fqn fqn = getFqn(args, m.getMethodId());
 66  1904 WorkspaceNode workspaceNode = fetchWorkspaceNode(fqn, workspace, true);
 67   
 68    // in the case of a data gravitation cleanup, if the primary Fqn does not exist the backup one may.
 69  1904 if (workspaceNode == null && m.getMethodId() == MethodDeclarations.dataGravitationCleanupMethod_id)
 70    {
 71  4 workspaceNode = fetchWorkspaceNode(getBackupFqn(args), workspace, true);
 72    }
 73   
 74  1904 if (workspaceNode != null)
 75    {
 76    // use explicit versioning
 77  1881 if (ctx.getOptionOverrides() != null && ctx.getOptionOverrides().getDataVersion() != null)
 78    {
 79    // if the method call is a move() then barf. Note that remove calls will set data versions explicitly, regardless.
 80  222 if (ctx.isOriginLocal() && m.getMethodId() == MethodDeclarations.moveMethodLocal_id)
 81  0 throw new CacheException("Setting a data version while performing a move() is not supported!!");
 82   
 83  222 workspace.setVersioningImplicit(false);
 84  222 DataVersion version = ctx.getOptionOverrides().getDataVersion();
 85   
 86  222 workspaceNode.setVersion(version);
 87  0 if (trace) log.trace("Setting versioning for node " + workspaceNode.getFqn() + " to explicit");
 88   
 89  222 workspaceNode.setVersioningImplicit(false);
 90    }
 91    else
 92    {
 93  0 if (trace) log.trace("Setting versioning for node " + workspaceNode.getFqn() + " to implicit");
 94  1659 workspaceNode.setVersioningImplicit(true);
 95    }
 96    }
 97    else
 98    {
 99    // "fail-more-silently" patch thanks to Owen Taylor - JBCACHE-767
 100  23 if ((ctx.getOptionOverrides() == null || !ctx.getOptionOverrides().isFailSilently()) && MethodDeclarations.isPutMethod(m.getMethodId()))
 101    {
 102  0 throw new CacheException("Unable to set node version for " + fqn + ", node is null.");
 103    }
 104    }
 105   
 106  1904 switch (m.getMethodId())
 107    {
 108  28 case MethodDeclarations.moveMethodLocal_id:
 109  28 Fqn parentFqn = (Fqn) args[1];
 110  28 moveNodeAndNotify(parentFqn, workspaceNode, workspace, ctx);
 111  28 break;
 112  235 case MethodDeclarations.putDataMethodLocal_id:
 113  235 putDataMapAndNotify((Map<Object, Object>) args[2], false, workspace, workspaceNode, ctx);
 114  235 break;
 115  2 case MethodDeclarations.putDataEraseMethodLocal_id:
 116  2 putDataMapAndNotify((Map<Object, Object>) args[2], (Boolean) args[args.length - 1], workspace, workspaceNode, ctx);
 117  2 break;
 118  1415 case MethodDeclarations.putKeyValMethodLocal_id:
 119  16 case MethodDeclarations.putForExternalReadMethodLocal_id:
 120  1431 Object key = args[2];
 121  1431 Object value = args[3];
 122  1431 result = putDataKeyValueAndNotify(key, value, workspace, workspaceNode, ctx);
 123  1431 break;
 124  170 case MethodDeclarations.removeNodeMethodLocal_id:
 125  170 result = removeNode(workspace, workspaceNode, true, ctx);
 126  170 break;
 127  21 case MethodDeclarations.removeKeyMethodLocal_id:
 128  21 Object removeKey = args[2];
 129  21 result = removeKeyAndNotify(removeKey, workspace, workspaceNode, ctx);
 130  21 break;
 131  10 case MethodDeclarations.removeDataMethodLocal_id:
 132  10 removeDataAndNotify(workspace, workspaceNode, ctx);
 133  10 break;
 134  7 case MethodDeclarations.dataGravitationCleanupMethod_id:
 135  7 result = super.invoke(ctx);
 136  0 default:
 137  7 if (log.isWarnEnabled()) log.warn("Cannot handle CRUD method " + m);
 138  7 break;
 139    }
 140   
 141  1904 addToModificationList(gtx, m, ctx);
 142    }
 143    else
 144    {
 145  1052711 switch (m.getMethodId())
 146    {
 147  1050374 case MethodDeclarations.getKeyValueMethodLocal_id:
 148  1050374 result = getValueForKeyAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
 149  1050373 break;
 150  34 case MethodDeclarations.getKeysMethodLocal_id:
 151  34 result = getKeysAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
 152  34 break;
 153  282 case MethodDeclarations.getChildrenNamesMethodLocal_id:
 154  282 result = getChildNamesAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
 155  282 break;
 156  1247 case MethodDeclarations.getNodeMethodLocal_id:
 157  1247 result = getNodeAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
 158  1247 break;
 159  774 default:
 160  0 if (trace) log.trace("read Method " + m + " called - Not handling, passing on.");
 161  774 result = super.invoke(ctx);
 162  772 break;
 163    }
 164    }
 165  1054612 return result;
 166    }
 167   
 168    /**
 169    * Extracts the Fqn from the array of arguments passed in
 170    *
 171    * @param args array of args
 172    * @return fqn
 173    */
 174  1904 private Fqn getFqn(Object[] args, int methodId)
 175    {
 176  1904 return (Fqn) args[methodId == MethodDeclarations.moveMethodLocal_id ? 0 : 1];
 177    }
 178   
 179    /**
 180    * Retrieves a backup fqn in an array of arguments. This is typically used to parse arguments from a data gravitation cleanup method.
 181    *
 182    * @param args array of arguments to parse
 183    * @return an Fqn
 184    */
 185  4 private Fqn getBackupFqn(Object[] args)
 186    {
 187  4 return (Fqn) args[2];
 188    }
 189   
 190    /**
 191    * Adds a method call to the modification list of a given transaction's transaction entry
 192    *
 193    * @param gtx transaction
 194    * @param m methodcall to add
 195    */
 196  1904 private void addToModificationList(GlobalTransaction gtx, MethodCall m, InvocationContext ctx)
 197    {
 198  1904 Option opt = ctx.getOptionOverrides();
 199  1904 if (opt == null || !opt.isCacheModeLocal())
 200    {
 201  1842 txTable.addModification(gtx, m);
 202  0 if (log.isDebugEnabled()) log.debug("Adding Method " + m + " to modification list");
 203    }
 204  107 if (cache.getCacheLoaderManager() != null) txTable.addCacheLoaderModification(gtx, m);
 205   
 206    }
 207   
 208    // -----------------------------------------------------------------
 209   
 210    // Methods that mimic core functionality in the cache, except that they work on WorkspaceNodes in the TransactionWorkspace.
 211   
 212    // -----------------------------------------------------------------
 213   
 214   
 215    /**
 216    * Performs a move within the workspace.
 217    *
 218    * @param parentFqn parent under which the node is to be moved
 219    * @param node node to move
 220    * @param ws transaction workspace
 221    * @param ctx
 222    */
 223  28 private void moveNodeAndNotify(Fqn parentFqn, WorkspaceNode node, TransactionWorkspace ws, InvocationContext ctx)
 224    {
 225  28 Fqn nodeFqn = node.getFqn();
 226  28 if (nodeFqn.isRoot())
 227    {
 228  0 log.warn("Attempting to move the root node. Not taking any action, treating this as a no-op.");
 229  0 return;
 230    }
 231    // retrieve parent
 232  28 WorkspaceNode parent = fetchWorkspaceNode(parentFqn, ws, false);
 233  0 if (parent == null) throw new NodeNotExistsException("Node " + parentFqn + " does not exist!");
 234   
 235  28 WorkspaceNode oldParent = fetchWorkspaceNode(nodeFqn.getParent(), ws, false);
 236  0 if (oldParent == null) throw new NodeNotExistsException("Node " + nodeFqn.getParent() + " does not exist!");
 237   
 238  28 Object nodeName = nodeFqn.getLastElement();
 239   
 240    // now that we have the parent and target nodes:
 241    // first correct the pointers at the pruning point
 242  28 oldParent.removeChild(new Fqn(nodeName));
 243   
 244    // parent pointer is calculated on the fly using Fqns.
 245    // now adjust Fqns of node and all children.
 246  28 Fqn nodeNewFqn = new Fqn(parent.getFqn(), nodeFqn.getLastElement());
 247   
 248    // pre-notify
 249  28 notifier.notifyNodeMoved(nodeFqn, nodeNewFqn, true, ctx);
 250  28 recursiveMoveNode(node, parent.getFqn(), ws);
 251   
 252    // remove old nodes. this may mark some nodes which have already been moved as deleted
 253  28 removeNode(ws, node, false, ctx);
 254   
 255    // post-notify
 256  28 notifier.notifyNodeMoved(nodeFqn, nodeNewFqn, false, ctx);
 257    }
 258   
 259    /**
 260    * Moves a node to a new base.
 261    *
 262    * @param node node to move
 263    * @param newBase new base Fqn under which the given node will now exist
 264    * @param ws transaction workspace
 265    */
 266  38 private void recursiveMoveNode(WorkspaceNode node, Fqn newBase, TransactionWorkspace ws)
 267    {
 268  38 Fqn newFqn = new Fqn(newBase, node.getFqn().getLastElement());
 269  38 WorkspaceNode movedNode = fetchWorkspaceNode(newFqn, ws, true);
 270  38 movedNode.putAll(node.getData());
 271   
 272    // process children
 273  38 for (Object n : node.getChildrenNames())
 274    {
 275  10 WorkspaceNode child = fetchWorkspaceNode(new Fqn(node.getFqn(), n), ws, false);
 276  10 if (child != null) recursiveMoveNode(child, newFqn, ws);
 277    }
 278    }
 279   
 280  237 private void putDataMapAndNotify(Map<Object, Object> data, boolean eraseExisitng, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
 281    {
 282  237 if (workspaceNode == null)
 283  0 throw new NodeNotExistsException("optimisticCreateIfNotExistsInterceptor should have created this node!");
 284    // pre-notify
 285  237 notifier.notifyNodeModified(workspaceNode.getFqn(), true, PUT_MAP, workspaceNode.getData(), ctx);
 286  2 if (eraseExisitng) workspaceNode.clearData();
 287  237 workspaceNode.putAll(data);
 288  237 workspace.addNode(workspaceNode);
 289    // post-notify
 290  237 notifier.notifyNodeModified(workspaceNode.getFqn(), false, PUT_MAP, workspaceNode.getData(), ctx);
 291    }
 292   
 293  1431 private Object putDataKeyValueAndNotify(Object key, Object value, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
 294    {
 295  1431 if (workspaceNode == null)
 296  0 throw new NodeNotExistsException("optimisticCreateIfNotExistsInterceptor should have created this node!");
 297   
 298  1431 Map addedData = Collections.singletonMap(key, value);
 299    // pre-notify
 300  1431 notifier.notifyNodeModified(workspaceNode.getFqn(), true, PUT_DATA, workspaceNode.getData(), ctx);
 301   
 302  1431 Object old = workspaceNode.put(key, value);
 303  1431 workspace.addNode(workspaceNode);
 304   
 305    // post-notify
 306  1431 notifier.notifyNodeModified(workspaceNode.getFqn(), false, PUT_DATA, addedData, ctx);
 307   
 308  1431 return old;
 309    }
 310   
 311  198 private boolean removeNode(TransactionWorkspace workspace, WorkspaceNode workspaceNode, boolean notify, InvocationContext ctx) throws CacheException
 312    {
 313    // it is already removed - we can ignore it
 314  20 if (workspaceNode == null) return false;
 315   
 316  178 Fqn parentFqn = workspaceNode.getFqn().getParent();
 317  178 WorkspaceNode parentNode = fetchWorkspaceNode(parentFqn, workspace, true);
 318  0 if (parentNode == null) throw new NodeNotExistsException("Unable to find parent node with fqn " + parentFqn);
 319   
 320    // pre-notify
 321  150 if (notify) notifier.notifyNodeRemoved(workspaceNode.getFqn(), true, workspaceNode.getData(), ctx);
 322   
 323  178 parentNode.removeChild(workspaceNode.getFqn().getLastElement());
 324   
 325  178 Fqn nodeFqn = workspaceNode.getFqn();
 326   
 327  178 SortedMap<Fqn, WorkspaceNode> tailMap = workspace.getNodesAfter(workspaceNode.getFqn());
 328   
 329  178 for (WorkspaceNode toDelete : tailMap.values())
 330    {
 331  262 if (toDelete.getFqn().isChildOrEquals(nodeFqn))
 332    {
 333  0 if (trace) log.trace("marking node " + toDelete.getFqn() + " as deleted");
 334  199 toDelete.markAsDeleted(true);
 335    }
 336    else
 337    {
 338  63 break;// no more children, we came to the end
 339    }
 340    }
 341   
 342    // post-notify
 343  150 if (notify) notifier.notifyNodeRemoved(workspaceNode.getFqn(), false, null, ctx);
 344  178 return true;
 345    }
 346   
 347  21 private Object removeKeyAndNotify(Object removeKey, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
 348    {
 349  1 if (workspaceNode == null) return null;
 350   
 351    // pre-notify
 352  20 notifier.notifyNodeModified(workspaceNode.getFqn(), true, REMOVE_DATA, workspaceNode.getData(), ctx);
 353   
 354  20 Object old = workspaceNode.remove(removeKey);
 355  20 workspace.addNode(workspaceNode);
 356   
 357  20 Map removedData = Collections.singletonMap(removeKey, old);
 358    // post-notify
 359  20 notifier.notifyNodeModified(workspaceNode.getFqn(), false, REMOVE_DATA, removedData, ctx);
 360   
 361  20 return old;
 362    }
 363   
 364  10 private void removeDataAndNotify(TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
 365    {
 366  1 if (workspaceNode == null) return;
 367   
 368  9 Map data = new HashMap(workspaceNode.getData());
 369   
 370    // pre-notify
 371  9 notifier.notifyNodeModified(workspaceNode.getFqn(), true, REMOVE_DATA, data, ctx);
 372   
 373  9 workspaceNode.clearData();
 374  9 workspace.addNode(workspaceNode);
 375   
 376    // post-notify
 377  9 notifier.notifyNodeModified(workspaceNode.getFqn(), false, REMOVE_DATA, data, ctx);
 378    }
 379   
 380  1050373 private Object getValueForKeyAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
 381    {
 382  1050373 Fqn fqn = (Fqn) args[0];
 383  1050373 Object key = args[1];
 384  1050373 WorkspaceNode workspaceNode = fetchWorkspaceNode(fqn, workspace, false);
 385   
 386  1050373 if (workspaceNode == null)
 387    {
 388  0 if (trace) log.debug("Unable to find node " + fqn + " in workspace.");
 389  504 return null;
 390    }
 391    else
 392    {
 393    //add this node into the wrokspace
 394  1049869 notifier.notifyNodeVisited(fqn, true, ctx);
 395  1049869 Object val = workspaceNode.get(key);
 396  1049869 workspace.addNode(workspaceNode);
 397  1049869 notifier.notifyNodeVisited(fqn, false, ctx);
 398  1049869 return val;
 399    }
 400    }
 401   
 402  1247 private Object getNodeAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
 403    {
 404  1247 Fqn fqn = (Fqn) args[0];
 405   
 406  1247 WorkspaceNode workspaceNode = fetchWorkspaceNode(fqn, workspace, false);
 407   
 408  1247 if (workspaceNode == null)
 409    {
 410  0 if (trace) log.trace("Unable to find node " + fqn + " in workspace.");
 411  93 return null;
 412    }
 413    else
 414    {
 415  1154 notifier.notifyNodeVisited(fqn, true, ctx);
 416  1154 workspace.addNode(workspaceNode);
 417  1154 notifier.notifyNodeVisited(fqn, false, ctx);
 418  1154 return workspaceNode.getNode();
 419    }
 420    }
 421   
 422  34 private Object getKeysAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
 423    {
 424  34 Fqn fqn = (Fqn) args[0];
 425   
 426  34 WorkspaceNode workspaceNode = fetchWorkspaceNode(fqn, workspace, false);
 427   
 428  34 if (workspaceNode == null)
 429    {
 430  0 if (trace) log.trace("unable to find node " + fqn + " in workspace.");
 431  0 return null;
 432    }
 433    else
 434    {
 435  34 notifier.notifyNodeVisited(fqn, true, ctx);
 436  34 Object keySet = workspaceNode.getKeys();
 437  34 workspace.addNode(workspaceNode);
 438  34 notifier.notifyNodeVisited(fqn, false, ctx);
 439  34 return keySet;
 440    }
 441    }
 442   
 443  282 private Object getChildNamesAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
 444    {
 445  282 Fqn fqn = (Fqn) args[0];
 446   
 447  282 WorkspaceNode workspaceNode = fetchWorkspaceNode(fqn, workspace, false);
 448   
 449  282 if (workspaceNode == null)
 450    {
 451  0 if (trace) log.trace("Unable to find node " + fqn + " in workspace.");
 452  0 return null;
 453    }
 454    else
 455    {
 456  282 notifier.notifyNodeVisited(fqn, true, ctx);
 457  282 Object nameSet = workspaceNode.getChildrenNames();
 458  282 workspace.addNode(workspaceNode);
 459  282 notifier.notifyNodeVisited(fqn, false, ctx);
 460  282 return nameSet;
 461    }
 462    }
 463   
 464    // -----------------------------------------------------------------
 465   
 466    // Methods to help retrieval of nodes from the transaction workspace.
 467   
 468    // -----------------------------------------------------------------
 469   
 470    /**
 471    * Retrieves a node for a given Fqn from the workspace. If the node does not exist in the workspace it is retrieved
 472    * from the cache's data structure. Note that at no point is a NEW node created in the underlying data structure.
 473    * That is up to the {@link org.jboss.cache.interceptors.OptimisticCreateIfNotExistsInterceptor}.
 474    * <p/>
 475    * If the node requested is in the workspace but marked as deleted, this method will NOT retrieve it, unless undeleteIfNecessary
 476    * is true, in which case the node's <tt>deleted</tt> property is set to false first before being retrieved.
 477    *
 478    * @param fqn Fqn of the node to retrieve
 479    * @param workspace transaction workspace to look in
 480    * @param undeleteIfNecessary if the node is in the workspace but marked as deleted, this meth
 481    * @return a node, if found, or null if not.
 482    */
 483  1054138 private WorkspaceNode fetchWorkspaceNode(Fqn fqn, TransactionWorkspace workspace, boolean undeleteIfNecessary)
 484    {
 485  1054138 WorkspaceNode workspaceNode = workspace.getNode(fqn);
 486    // if we do not have the node then we need to add it to the workspace
 487  1054138 if (workspaceNode == null)
 488    {
 489  1051764 NodeSPI node = cache.peek(fqn, true);
 490  615 if (node == null) return null;
 491   
 492    // create new workspace node based on the node from the underlying data structure
 493  1051149 workspaceNode = nodeFactory.createWorkspaceNode(node, workspace);
 494   
 495    // and add the node to the workspace.
 496  1051149 workspace.addNode(workspaceNode);
 497    }
 498   
 499    // Check that the workspace node has been marked as deleted.
 500  1053523 if (workspaceNode.isDeleted())
 501    {
 502  0 if (trace) log.trace("Node " + fqn + " has been deleted in the workspace.");
 503  21 if (undeleteIfNecessary)
 504    {
 505  12 workspaceNode.markAsDeleted(false);
 506    // re-add to parent
 507  12 WorkspaceNode parent = fetchWorkspaceNode(fqn.getParent(), workspace, true);
 508  12 parent.addChild(workspaceNode);
 509    }
 510    else
 511    {
 512    // don't return deleted nodes if undeleteIfNecessary is false!
 513  9 workspaceNode = null;
 514    }
 515    }
 516   
 517    // set implicit node versioning flag.
 518  1053523 if (workspaceNode != null && !(workspaceNode.getVersion() instanceof DefaultDataVersion))
 519    {
 520  39 workspaceNode.setVersioningImplicit(false);
 521    }
 522   
 523  1053523 return workspaceNode;
 524    }
 525    }