Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 230   Methods: 5
NCLOC: 162   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
OptimisticValidatorInterceptor.java 72.5% 84.9% 100% 81.7%
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.Fqn;
 11    import org.jboss.cache.InvocationContext;
 12    import org.jboss.cache.NodeSPI;
 13    import org.jboss.cache.marshall.MethodCall;
 14    import org.jboss.cache.marshall.MethodDeclarations;
 15    import org.jboss.cache.optimistic.DataVersioningException;
 16    import org.jboss.cache.optimistic.DefaultDataVersion;
 17    import org.jboss.cache.optimistic.TransactionWorkspace;
 18    import org.jboss.cache.optimistic.WorkspaceNode;
 19    import org.jboss.cache.transaction.GlobalTransaction;
 20   
 21    import java.util.Collection;
 22    import java.util.List;
 23    import java.util.Map;
 24    import java.util.Set;
 25   
 26    /**
 27    * Validates the data in the {@link TransactionWorkspace} against data in the underlying data structure
 28    * (versions only) and then applies changes to the underlying data structure. This is only triggered when commit,
 29    * rollback or prepare method calls are encountered. Other method calls are directly passed up the interceptor chain,
 30    * untouched. Note that prepare/commit/rollbacks are <b>not</b> passed up the interceptor chain after being processed.
 31    * <p/>
 32    * When preparting, this interceptor does nothing more than validate versions.
 33    * The validation scheme used is based on the {@link org.jboss.cache.optimistic.DataVersion} implementation used.
 34    * {@link org.jboss.cache.optimistic.DataVersion#newerThan(org.jboss.cache.optimistic.DataVersion)} is used to determine
 35    * whether the version of one instance is newer than the version of another. It is up to the {@link org.jboss.cache.optimistic.DataVersion}
 36    * implementation to deal with attempting to compare incompatible version types (and potentially throwing {@link org.jboss.cache.optimistic.DataVersioningException}s.
 37    * <p/>
 38    * Upon successful commit, changes in the workspace are applied to the underlying data structure in the cache.
 39    * <p/>
 40    * On rollback clears the nodes in the workspace and leaves the underlying data structure untouched.
 41    *
 42    * @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
 43    * @author Steve Woodcock (<a href="mailto:stevew@jofti.com">stevew@jofti.com</a>)
 44    */
 45    public class OptimisticValidatorInterceptor extends OptimisticInterceptor
 46    {
 47  2107999 public Object invoke(InvocationContext ctx) throws Throwable
 48    {
 49  2107999 MethodCall m = ctx.getMethodCall();
 50  2107999 Object retval = null;
 51   
 52    // Methods we are interested in are prepare/commit
 53    // They do not go further than this interceptor
 54  2107999 switch (m.getMethodId())
 55    {
 56  1129 case MethodDeclarations.optimisticPrepareMethod_id:
 57    // should pass in a different prepare here
 58  1129 validateNodes(getGlobalTransaction(ctx));
 59  1100 break;
 60  1052381 case MethodDeclarations.commitMethod_id:
 61  1052381 commit(getGlobalTransaction(ctx));
 62  1052381 break;
 63  51 case MethodDeclarations.rollbackMethod_id:
 64  51 rollBack(getGlobalTransaction(ctx));
 65  51 break;
 66  1054438 default:
 67  1054438 retval = super.invoke(ctx);
 68  1054438 break;
 69    }
 70  2107970 return retval;
 71    }
 72   
 73  1129 private void validateNodes(GlobalTransaction gtx) throws CacheException
 74    {
 75  1129 TransactionWorkspace workspace = getTransactionWorkspace(gtx);
 76   
 77    // There is no guarantee that this collection is in any order!
 78  1129 Collection<WorkspaceNode> nodes = workspace.getNodes().values();
 79   
 80    //we ought to have all necessary locks here so lets try and validate
 81  0 if (log.isDebugEnabled()) log.debug("Validating " + nodes.size() + " nodes.");
 82  1129 simpleValidate(nodes);
 83  1100 log.debug("Successfully validated nodes");
 84    }
 85   
 86  1129 private void simpleValidate(Collection<WorkspaceNode> nodes) throws DataVersioningException
 87    {
 88  1129 for (WorkspaceNode workspaceNode : nodes)
 89    {
 90  2853 Fqn fqn = workspaceNode.getFqn();
 91  0 if (trace) log.trace("Validating version for node " + fqn);
 92   
 93  2853 NodeSPI underlyingNode;
 94  2853 underlyingNode = cache.peek(fqn, true);
 95   
 96    // if this is a newly created node then we expect the underlying node to be null.
 97    // if not, we have a problem...
 98  2853 if (underlyingNode == null && !workspaceNode.isCreated())
 99    {
 100  2 throw new DataVersioningException("Underlying node for " + fqn + " is null, and this node wasn't newly created in this transaction! We have a concurrent deletion event.");
 101    }
 102   
 103  2851 if (underlyingNode != null && workspaceNode.isCreated() && workspaceNode.isDirty())
 104    {
 105  6 throw new DataVersioningException("Transaction attempted to create " + fqn + " anew. It has already been created since this transaction started, by another (possibly remote) transaction. We have a concurrent creation event.");
 106    }
 107   
 108  2845 if (!workspaceNode.isCreated() && (workspaceNode.isDeleted() || workspaceNode.isDirty()))
 109    {
 110    // if there is a DataVersion type mismatch here, leave it up to the DataVersion impl to barf if necessary. - JBCACHE-962
 111  552 if (underlyingNode.getVersion().newerThan(workspaceNode.getVersion()))
 112    {
 113    // we have an out of date node here
 114  18 throw new DataVersioningException("Version mismatch for node " + fqn + ": underlying node with version " + workspaceNode.getNode().getVersion() + " is newer than workspace node, with version " + workspaceNode.getVersion());
 115    }
 116    }
 117    }
 118    }
 119   
 120   
 121  1052381 private void commit(GlobalTransaction gtx)
 122    {
 123  1052381 TransactionWorkspace workspace;
 124   
 125  1052381 try
 126    {
 127  1052381 workspace = getTransactionWorkspace(gtx);
 128    }
 129    catch (CacheException e)
 130    {
 131  0 log.warn("we can't rollback", e);
 132  0 return;
 133    }
 134   
 135  0 if (log.isDebugEnabled()) log.debug("Commiting successfully validated changes for GlobalTransaction " + gtx);
 136   
 137   
 138  1052381 Collection<WorkspaceNode> workspaceNodes = workspace.getNodes().values();
 139   
 140  1052381 for (WorkspaceNode workspaceNode : workspaceNodes)
 141    {
 142  1053331 NodeSPI underlyingNode = workspaceNode.getNode();
 143   
 144    // short circuit if this node is deleted?
 145  1053331 if (workspaceNode.isDeleted())
 146    {
 147  0 if (trace) log.trace("Workspace node " + workspaceNode.getFqn() + " deleted; removing");
 148   
 149  172 if (underlyingNode.getFqn().isRoot())
 150    {
 151  0 throw new CacheException("An illegal attempt to delete the root node!");
 152    }
 153    else
 154    {
 155  172 NodeSPI parent = underlyingNode.getParent();
 156  172 if (parent == null)
 157    {
 158  0 throw new CacheException("Underlying node " + underlyingNode + " has no parent");
 159    }
 160   
 161  172 parent.removeChildDirect(underlyingNode.getFqn().getLastElement());
 162    }
 163    }
 164    else
 165    {
 166  1053159 boolean updateVersion = false;
 167  1053159 if (workspaceNode.isChildrenModified())
 168    {
 169  1088 log.trace("Updating children since node has modified children");
 170    // merge children.
 171  1088 List<Set<Fqn>> deltas = workspaceNode.getMergedChildren();
 172   
 173  0 if (trace) log.trace("Applying children deltas to parent node " + underlyingNode.getFqn());
 174  1088 for (Fqn child : deltas.get(0))
 175    {
 176  1018 underlyingNode.addChildDirect(workspaceNode.getChild(child.getLastElement()));
 177    }
 178   
 179  1088 for (Fqn child : deltas.get(1))
 180    {
 181  153 underlyingNode.removeChildDirect(child.getLastElement());
 182    }
 183   
 184  1088 updateVersion = cache.getConfiguration().isLockParentForChildInsertRemove();
 185    // do we need to notify listeners of a modification?? If all we've done is added children then don't
 186    // notify.
 187    }
 188   
 189  1053159 if (workspaceNode.isDirty())
 190    {
 191  1204 log.trace("Merging data since node is dirty");
 192  1204 Map mergedData = workspaceNode.getMergedData();
 193  1204 underlyingNode.clearDataDirect();
 194  1204 underlyingNode.putAllDirect(mergedData);
 195  1204 updateVersion = true;
 196    }
 197   
 198  1053159 if (updateVersion)
 199    {
 200  1225 if (workspaceNode.isVersioningImplicit())
 201    {
 202  0 if (trace) log.trace("Versioning is implicit; incrementing.");
 203  1059 underlyingNode.setVersion(((DefaultDataVersion) workspaceNode.getVersion()).increment());
 204    }
 205    else
 206    {
 207  0 if (trace) log.trace("Versioning is explicit; not attempting an increment.");
 208  166 underlyingNode.setVersion(workspaceNode.getVersion());
 209    }
 210   
 211  1225 if (trace)
 212  0 log.trace("Setting version of node " + underlyingNode.getFqn() + " from " + workspaceNode.getVersion() + " to " + underlyingNode.getVersion());
 213    }
 214    else
 215    {
 216  1051934 if (trace)
 217  0 log.trace("Version update on " + workspaceNode.getFqn() + " not necessary since the node is not dirty or LockParentForChildInsertRemove is set to false");
 218    }
 219    }
 220    }
 221   
 222    }
 223   
 224  51 private void rollBack(GlobalTransaction gtx)
 225    {
 226  51 TransactionWorkspace workspace;
 227  51 workspace = getTransactionWorkspace(gtx);
 228  51 workspace.clearNodes();
 229    }
 230    }