Package org.jboss.cache.interceptors

Source Code of org.jboss.cache.interceptors.OptimisticValidatorInterceptor

/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.interceptors;

import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.NodeSPI;
import static org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.optimistic.DataVersioningException;
import org.jboss.cache.optimistic.DefaultDataVersion;
import org.jboss.cache.optimistic.TransactionWorkspace;
import org.jboss.cache.optimistic.WorkspaceNode;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jgroups.Address;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Validates the data in the {@link TransactionWorkspace} against data in the underlying data structure
* (versions only) and then applies changes to the underlying data structure.  This is only triggered when commit,
* rollback or prepare method calls are encountered.  Other method calls are directly passed up the interceptor chain,
* untouched.  Note that prepare/commit/rollbacks are <b>not</b> passed up the interceptor chain after being processed.
* <p/>
* When preparting, this interceptor does nothing more than validate versions.
* The validation scheme used is based on the {@link org.jboss.cache.optimistic.DataVersion} implementation used.
* {@link org.jboss.cache.optimistic.DataVersion#newerThan(org.jboss.cache.optimistic.DataVersion)} is used to determine
* whether the version of one instance is newer than the version of another.  It is up to the {@link org.jboss.cache.optimistic.DataVersion}
* implementation to deal with attempting to compare incompatible version types (and potentially throwing {@link org.jboss.cache.optimistic.DataVersioningException}s.
* <p/>
* Upon successful commit, changes in the workspace are applied to the underlying data structure in the cache.
* <p/>
* On rollback clears the nodes in the workspace and leaves the underlying data structure untouched.
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @author Steve Woodcock (<a href="mailto:stevew@jofti.com">stevew@jofti.com</a>)
*/
public class OptimisticValidatorInterceptor extends OptimisticInterceptor
{
   private boolean useTombstones;

   public OptimisticValidatorInterceptor()
   {
      initLogger();
   }

   @Override
   public void setCache(CacheSPI cache)
   {
      super.setCache(cache);
      CacheMode mode = cache.getConfiguration().getCacheMode();
      useTombstones = (mode == CacheMode.INVALIDATION_ASYNC) || (mode == CacheMode.INVALIDATION_SYNC);
   }

   @Override
   protected Object handleOptimisticPrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modifications, Map data, Address address, boolean onePhaseCommit) throws Throwable
   {
      TransactionWorkspace workspace = getTransactionWorkspace(getGlobalTransaction(ctx));

      // There is no guarantee that this collection is in any order!
      Collection<WorkspaceNode> nodes = workspace.getNodes().values();

      //we ought to have all necessary locks here so lets try and validate
      if (log.isDebugEnabled()) log.debug("Validating " + nodes.size() + " nodes.");
      for (WorkspaceNode workspaceNode : nodes)
      {
         if (workspaceNode.isDirty())
         {
            Fqn fqn = workspaceNode.getFqn();
            if (trace) log.trace("Validating version for node [" + fqn + "]");

            NodeSPI underlyingNode;
            underlyingNode = peekNode(ctx, fqn, false, true, true);

            // if this is a newly created node then we expect the underlying node to be null.
            // also, if the node has been deleted in the WS and the underlying node is null, this *may* be ok ... will test again later when comparing versions
            // if not, we have a problem...
            if (underlyingNode == null && !workspaceNode.isCreated() && !workspaceNode.isDeleted())
            {
               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.");
            }

            // needs to have been created AND modified - we allow concurrent creation if no data is put into the node
            if (underlyingNode != null && underlyingNode.isValid() && workspaceNode.isCreated() && workspaceNode.isModified())
            {
               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.");
            }

            if (underlyingNode != null && !underlyingNode.isValid())
            {
               // we havea  tombstone
               if (!workspaceNode.isCreated() && !workspaceNode.isDeleted())
                  throw new DataVersioningException("Underlying node doesn't exist but a tombstone does; workspace node should be marked as created!");
               if (underlyingNode.getVersion().newerThan(workspaceNode.getVersion()))
               {
                  // we have an out of date node here
                  throw new DataVersioningException("Version mismatch for node " + fqn + ": underlying node with version " + workspaceNode.getNode().getVersion() + " is newer than workspace node, with version " + workspaceNode.getVersion());
               }
            }

            if (!workspaceNode.isCreated() && (workspaceNode.isDeleted() || workspaceNode.isModified()))
            {
               // if the real node is null, throw a DVE
               if (underlyingNode == null)
               {
                  // but not if the WSN has also been deleted
                  if (!workspaceNode.isDeleted())
                     throw new DataVersioningException("Unable to compare versions since the underlying node has been deleted by a concurrent transaction!");
                  else if (trace)
                     log.trace("The data node [" + fqn + "] is null, but this is ok since the workspace node is marked as deleted as well");
               }
               // if there is a DataVersion type mismatch here, leave it up to the DataVersion impl to barf if necessary.  - JBCACHE-962
               else if (underlyingNode.getVersion().newerThan(workspaceNode.getVersion()))
               {
                  // we have an out of date node here
                  throw new DataVersioningException("Version mismatch for node " + fqn + ": underlying node with version " + workspaceNode.getNode().getVersion() + " is newer than workspace node, with version " + workspaceNode.getVersion());
               }
            }
         }
         else
         {
            if (trace) log.trace("Node [" + workspaceNode.getFqn() + "] doesn't need validating as it isn't dirty");
         }
      }
      log.debug("Successfully validated nodes");
      return nextInterceptor(ctx);
   }

   @Override
   protected Object handleCommitMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      GlobalTransaction gtx = getGlobalTransaction(ctx);

      TransactionWorkspace workspace;

      try
      {
         workspace = getTransactionWorkspace(gtx);
      }
      catch (CacheException e)
      {
         log.warn("we can't rollback", e);
         return nextInterceptor(ctx);
      }

      if (log.isDebugEnabled()) log.debug("Commiting successfully validated changes for GlobalTransaction " + gtx);


      Collection<WorkspaceNode> workspaceNodes = workspace.getNodes().values();

      for (WorkspaceNode workspaceNode : workspaceNodes)
      {
         NodeSPI underlyingNode = workspaceNode.getNode();

         // short circuit if this node is deleted?
         if (workspaceNode.isDeleted())
         {
            if (trace) log.trace("Workspace node " + workspaceNode.getFqn() + " deleted; removing");

            if (underlyingNode.getFqn().isRoot())
            {
               throw new CacheException("An illegal attempt to delete the root node!");
            }
            else
            {
               // mark it as invalid so any direct references are marked as such
               underlyingNode.setValid(false, true);
               // we need to update versions here, too
               performVersionUpdate(underlyingNode, workspaceNode);

               if (!useTombstones)
               {
                  // don't retain the tombstone
                  NodeSPI parent = underlyingNode.getParent();
                  if (parent == null)
                  {
                     throw new CacheException("Underlying node " + underlyingNode + " has no parent");
                  }

                  parent.removeChildDirect(underlyingNode.getFqn().getLastElement());
               }
            }
         }
         else
         {
            boolean updateVersion = false;
            if (workspaceNode.isChildrenModified() || workspaceNode.isResurrected()) // if it is newly created make sure we remove all underlying children that may exist, to solve a remove-and-create-in-tx condition
            {
               if (trace) log.trace("Updating children since node has modified children");
               // merge children.
               List<Set<Fqn>> deltas = workspaceNode.getMergedChildren();

               if (trace) log.trace("Applying children deltas to parent node " + underlyingNode.getFqn());

               if (workspaceNode.isResurrected())
               {
                  // mark it as invalid so any direct references are marked as such
                  Map childNode = underlyingNode.getChildrenMapDirect();
                  if (childNode != null)
                  {
                     for (Object o : childNode.values())
                     {
                        NodeSPI cn = (NodeSPI) o;
                        cn.setValid(false, true);
                        if (!useTombstones) underlyingNode.removeChildDirect(cn.getFqn().getLastElement());
                     }
                  }
               }

               for (Fqn child : deltas.get(0))
               {
                  NodeSPI childNode = workspace.getNode(child).getNode();
                  underlyingNode.addChildDirect(childNode);
                  childNode.setValid(true, false);
               }

               for (Fqn child : deltas.get(1))
               {
                  // mark it as invalid so any direct references are marked as such
                  NodeSPI childNode = underlyingNode.getChildDirect(child.getLastElement());
                  if (childNode != null) childNode.setValid(false, true);

                  if (!useTombstones) underlyingNode.removeChildDirect(child.getLastElement());
               }

               updateVersion = underlyingNode.isLockForChildInsertRemove();

               // do we need to notify listeners of a modification??  If all we've done is added children then don't
               // notify.
            }

            if (workspaceNode.isModified())
            {
               if (trace) log.trace("Merging data since node is dirty");
               Map mergedData = workspaceNode.getMergedData();
               underlyingNode.clearDataDirect();
               underlyingNode.putAllDirect(mergedData);

               // mark node and any parents as valid- if available.  Versioning parents are tough though - leave as old versions?
               validateNodeAndParents(underlyingNode);
               updateVersion = true;
            }

            if (updateVersion)
            {
               performVersionUpdate(underlyingNode, workspaceNode);
            }
         }
      }
      return nextInterceptor(ctx);
   }

   @Override
   protected Object handleRollbackMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      TransactionWorkspace workspace;
      workspace = getTransactionWorkspace(getGlobalTransaction(ctx));
      workspace.clearNodes();
      return nextInterceptor(ctx);
   }


   private void validateNodeAndParents(NodeSPI node)
   {
      node.setValid(true, false);
      if (!node.getFqn().isRoot()) validateNodeAndParents(node.getParent());
   }

   private void performVersionUpdate(NodeSPI underlyingNode, WorkspaceNode workspaceNode)
   {
      if (workspaceNode.isVersioningImplicit())
      {
         if (trace) log.trace("Versioning is implicit; incrementing.");
         underlyingNode.setVersion(((DefaultDataVersion) workspaceNode.getVersion()).increment());
      }
      else
      {
         if (trace) log.trace("Versioning is explicit; not attempting an increment.");
         underlyingNode.setVersion(workspaceNode.getVersion());
      }

      if (trace)
         log.trace("Setting version of node " + underlyingNode.getFqn() + " from " + workspaceNode.getVersion() + " to " + underlyingNode.getVersion());
   }

}
TOP

Related Classes of org.jboss.cache.interceptors.OptimisticValidatorInterceptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.