Package org.jboss.cache.interceptors

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

/*
* 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.NodeFactory;
import org.jboss.cache.NodeNotExistsException;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.Option;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jboss.cache.notifications.Notifier;
import static org.jboss.cache.notifications.event.NodeModifiedEvent.ModificationType.*;
import org.jboss.cache.optimistic.DataVersion;
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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;

/**
* Operations on nodes are done on the copies that exist in the workspace rather than passed down
* to the {@link org.jboss.cache.interceptors.CallInterceptor}.  These operations happen in this interceptor.
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
* @author <a href="mailto:stevew@jofti.com">Steve Woodcock (stevew@jofti.com)</a>
*/
public class OptimisticNodeInterceptor extends OptimisticInterceptor
{
   /**
    * Needed for the creation of workspace nodes based on underlying nodes in the cache.
    */
   private NodeFactory nodeFactory;
   private Notifier notifier;
   private long lockAcquisitionTimeout;

   @Inject
   protected void injectDependencies(Notifier notifier, NodeFactory nodeFactory)
   {
      this.notifier = notifier;
      this.nodeFactory = nodeFactory;
   }

   @Override
   public void setCache(CacheSPI cache)
   {
      super.setCache(cache);
      lockAcquisitionTimeout = cache.getConfiguration().getLockAcquisitionTimeout();
   }

   public OptimisticNodeInterceptor()
   {
      initLogger();
   }

   @Override
   public Object invoke(InvocationContext ctx) throws Throwable
   {
      MethodCall m = ctx.getMethodCall();
      Object[] args = m.getArgs();

      Object result = null;

      if (MethodDeclarations.isCrudMethod(m.getMethodId()))
      {
         GlobalTransaction gtx = getGlobalTransaction(ctx);
         TransactionWorkspace workspace = getTransactionWorkspace(gtx);
         Fqn fqn = getFqn(args, m.getMethodId());
         WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, MethodDeclarations.removeNodeMethodLocal_id != m.getMethodId(), true);

         // in the case of a data gravitation cleanup, if the primary Fqn does not exist the backup one may.
         if (workspaceNode == null && m.getMethodId() == MethodDeclarations.dataGravitationCleanupMethod_id)
         {
            workspaceNode = fetchWorkspaceNode(ctx, getBackupFqn(args), workspace, true, true);
         }

         if (workspaceNode != null)
         {
            // use explicit versioning
            if (ctx.getOptionOverrides() != null && ctx.getOptionOverrides().getDataVersion() != null)
            {
               // if the method call is a move() then barf.  Note that remove calls will set data versions explicitly, regardless.
               if (ctx.isOriginLocal() && m.getMethodId() == MethodDeclarations.moveMethodLocal_id)
                  throw new CacheException("Setting a data version while performing a move() is not supported!!");

               workspace.setVersioningImplicit(false);
               DataVersion version = ctx.getOptionOverrides().getDataVersion();

               workspaceNode.setVersion(version);
               if (trace) log.trace("Setting versioning for node " + workspaceNode.getFqn() + " to explicit");

               workspaceNode.setVersioningImplicit(false);
            }
            else
            {
               if (trace) log.trace("Setting versioning for node " + workspaceNode.getFqn() + " to implicit");
               workspaceNode.setVersioningImplicit(true);
            }
         }
         else
         {
            // "fail-more-silently" patch thanks to Owen Taylor - JBCACHE-767
            if ((ctx.getOptionOverrides() == null || !ctx.getOptionOverrides().isFailSilently()) && MethodDeclarations.isPutMethod(m.getMethodId()))
            {
               throw new CacheException("Unable to set node version for " + fqn + ", node is null.");
            }
         }

         switch (m.getMethodId())
         {
            case MethodDeclarations.moveMethodLocal_id:
               Fqn parentFqn = (Fqn) args[1];
               moveNodeAndNotify(parentFqn, workspaceNode, workspace, ctx);
               break;
            case MethodDeclarations.putDataMethodLocal_id:
               putDataMapAndNotify((Map<Object, Object>) args[2], false, workspace, workspaceNode, ctx);
               break;
            case MethodDeclarations.putDataEraseMethodLocal_id:
               putDataMapAndNotify((Map<Object, Object>) args[2], (Boolean) args[args.length - 1], workspace, workspaceNode, ctx);
               break;
            case MethodDeclarations.putKeyValMethodLocal_id:
            case MethodDeclarations.putForExternalReadMethodLocal_id:
               Object key = args[2];
               Object value = args[3];
               result = putDataKeyValueAndNotify(key, value, workspace, workspaceNode, ctx);
               break;
            case MethodDeclarations.removeNodeMethodLocal_id:
               result = removeNode(workspace, workspaceNode, true, ctx);
               break;
            case MethodDeclarations.removeKeyMethodLocal_id:
               Object removeKey = args[2];
               result = removeKeyAndNotify(removeKey, workspace, workspaceNode, ctx);
               break;
            case MethodDeclarations.removeDataMethodLocal_id:
               removeDataAndNotify(workspace, workspaceNode, ctx);
               break;
            case MethodDeclarations.dataGravitationCleanupMethod_id:
               result = nextInterceptor(ctx);
            default:
               if (log.isWarnEnabled()) log.warn("Cannot handle CRUD method " + m);
               break;
         }

         addToModificationList(gtx, m, ctx);
      }
      else
      {
         switch (m.getMethodId())
         {
            case MethodDeclarations.getKeyValueMethodLocal_id:
               result = getValueForKeyAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
               break;
            case MethodDeclarations.getKeysMethodLocal_id:
               result = getKeysAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
               break;
            case MethodDeclarations.getDataMapMethodLocal_id:
               result = getDataAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
               break;
            case MethodDeclarations.getChildrenNamesMethodLocal_id:
               result = getChildNamesAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
               break;
            case MethodDeclarations.getNodeMethodLocal_id:
               result = getNodeAndNotify(args, getTransactionWorkspace(getGlobalTransaction(ctx)), ctx);
               break;
            default:
               if (trace) log.trace("read Method " + m + " called - Not handling, passing on.");
               result = nextInterceptor(ctx);
               break;
         }
      }
      return result;
   }

   /**
    * Extracts the Fqn from the array of arguments passed in
    *
    * @param args array of args
    * @return fqn
    */
   private Fqn getFqn(Object[] args, int methodId)
   {
      return (Fqn) args[methodId == MethodDeclarations.moveMethodLocal_id || methodId == MethodDeclarations.dataGravitationCleanupMethod_id ? 0 : 1];
   }

   /**
    * Retrieves a backup fqn in an array of arguments.  This is typically used to parse arguments from a data gravitation cleanup method.
    *
    * @param args array of arguments to parse
    * @return an Fqn
    */
   private Fqn getBackupFqn(Object[] args)
   {
      return (Fqn) args[1];
   }

   /**
    * Adds a method call to the modification list of a given transaction's transaction entry
    *
    * @param gtx transaction
    * @param m   methodcall to add
    */
   private void addToModificationList(GlobalTransaction gtx, MethodCall m, InvocationContext ctx)
   {
      Option opt = ctx.getOptionOverrides();
      if (opt == null || !opt.isCacheModeLocal())
      {
         txTable.addModification(gtx, m);
         if (log.isDebugEnabled()) log.debug("Adding Method " + m + " to modification list");
      }
      if (cache.getCacheLoaderManager() != null) txTable.addCacheLoaderModification(gtx, m);

   }

   // -----------------------------------------------------------------

   // Methods that mimic core functionality in the cache, except that they work on WorkspaceNodes in the TransactionWorkspace.

   // -----------------------------------------------------------------


   /**
    * Performs a move within the workspace.
    *
    * @param parentFqn parent under which the node is to be moved
    * @param node      node to move
    * @param ws        transaction workspace
    * @param ctx
    */
   private void moveNodeAndNotify(Fqn parentFqn, WorkspaceNode node, TransactionWorkspace ws, InvocationContext ctx)
   {
      Fqn nodeFqn = node.getFqn();
      if (nodeFqn.isRoot())
      {
         log.warn("Attempting to move the root node.  Not taking any action, treating this as a no-op.");
         return;
      }

      WorkspaceNode oldParent = fetchWorkspaceNode(ctx, nodeFqn.getParent(), ws, false, true);
      if (oldParent == null) throw new NodeNotExistsException("Node " + nodeFqn.getParent() + " does not exist!");

      if (parentFqn.equals(oldParent.getFqn()))
      {
         log.warn("Attempting to move a node in same place.  Not taking any action, treating this as a no-op.");
         return;
      }
      // retrieve parent
      WorkspaceNode parent = fetchWorkspaceNode(ctx, parentFqn, ws, false, true);
      if (parent == null) throw new NodeNotExistsException("Node " + parentFqn + " does not exist!");

      Object nodeName = nodeFqn.getLastElement();

      // now that we have the parent and target nodes:
      // first correct the pointers at the pruning point
      oldParent.removeChild(new Fqn(nodeName));

      // parent pointer is calculated on the fly using Fqns.
      // now adjust Fqns of node and all children.
      Fqn nodeNewFqn = new Fqn(parent.getFqn(), nodeFqn.getLastElement());

      // pre-notify
      notifier.notifyNodeMoved(nodeFqn, nodeNewFqn, true, ctx);
      recursiveMoveNode(ctx, node, parent.getFqn(), ws);

      // remove old nodes. this may mark some nodes which have already been moved as deleted
      removeNode(ws, node, false, ctx);

      // post-notify
      notifier.notifyNodeMoved(nodeFqn, nodeNewFqn, false, ctx);
   }

   /**
    * Moves a node to a new base.
    *
    * @param node    node to move
    * @param newBase new base Fqn under which the given node will now exist
    * @param ws      transaction workspace
    */
   private void recursiveMoveNode(InvocationContext ctx, WorkspaceNode node, Fqn newBase, TransactionWorkspace ws)
   {
      Fqn newFqn = new Fqn(newBase, node.getFqn().getLastElement());
      WorkspaceNode movedNode = fetchWorkspaceNode(ctx, newFqn, ws, true, true);
      movedNode.putAll(node.getData());

      // process children
      for (Object n : node.getChildrenNames())
      {
         WorkspaceNode child = fetchWorkspaceNode(ctx, new Fqn(node.getFqn(), n), ws, false, true);
         if (child != null) recursiveMoveNode(ctx, child, newFqn, ws);
      }
   }

   private void putDataMapAndNotify(Map<Object, Object> data, boolean eraseExisitng, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
   {
      if (workspaceNode == null)
         throw new NodeNotExistsException("optimisticCreateIfNotExistsInterceptor should have created this node!");
      // pre-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), true, PUT_MAP, workspaceNode.getData(), ctx);
      if (eraseExisitng) workspaceNode.clearData();
      workspaceNode.putAll(data);
      workspace.addNode(workspaceNode);
      // post-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), false, PUT_MAP, workspaceNode.getData(), ctx);
   }

   private Object putDataKeyValueAndNotify(Object key, Object value, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
   {
      if (workspaceNode == null)
         throw new NodeNotExistsException("optimisticCreateIfNotExistsInterceptor should have created this node!");

      Map addedData = Collections.singletonMap(key, value);
      // pre-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), true, PUT_DATA, workspaceNode.getData(), ctx);

      Object old = workspaceNode.put(key, value);
      workspace.addNode(workspaceNode);

      // post-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), false, PUT_DATA, addedData, ctx);

      return old;
   }

   private boolean removeNode(TransactionWorkspace workspace, WorkspaceNode workspaceNode, boolean notify, InvocationContext ctx) throws CacheException
   {
      // it is already removed - we can ignore it
      if (workspaceNode == null) return false;

      Fqn parentFqn = workspaceNode.getFqn().getParent();
      WorkspaceNode parentNode = fetchWorkspaceNode(ctx, parentFqn, workspace, false, true);
      if (parentNode == null) throw new NodeNotExistsException("Unable to find parent node with fqn " + parentFqn);

      // pre-notify
      if (notify) notifier.notifyNodeRemoved(workspaceNode.getFqn(), true, workspaceNode.getData(), ctx);

      Fqn nodeFqn = workspaceNode.getFqn();
      parentNode.removeChild(nodeFqn.getLastElement());

      SortedMap<Fqn, WorkspaceNode> tailMap = workspace.getNodesAfter(workspaceNode.getFqn());

      for (WorkspaceNode toDelete : tailMap.values())
      {
         if (toDelete.getFqn().isChildOrEquals(nodeFqn))
         {
            if (trace) log.trace("marking node " + toDelete.getFqn() + " as deleted");
            toDelete.markAsDeleted(true);
         }
         else
         {
            break;// no more children, we came to the end
         }
      }

      // post-notify
      if (notify) notifier.notifyNodeRemoved(workspaceNode.getFqn(), false, null, ctx);
      return workspaceNode.getNode().isValid();
   }

   private Object removeKeyAndNotify(Object removeKey, TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
   {
      if (workspaceNode == null) return null;

      // pre-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), true, REMOVE_DATA, workspaceNode.getData(), ctx);

      Object old = workspaceNode.remove(removeKey);
      workspace.addNode(workspaceNode);

      Map removedData = Collections.singletonMap(removeKey, old);
      // post-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), false, REMOVE_DATA, removedData, ctx);

      return old;
   }

   private void removeDataAndNotify(TransactionWorkspace workspace, WorkspaceNode workspaceNode, InvocationContext ctx)
   {
      if (workspaceNode == null) return;

      Map data = new HashMap(workspaceNode.getData());

      // pre-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), true, REMOVE_DATA, data, ctx);

      workspaceNode.clearData();
      workspace.addNode(workspaceNode);

      // post-notify
      notifier.notifyNodeModified(workspaceNode.getFqn(), false, REMOVE_DATA, data, ctx);
   }

   private Object getValueForKeyAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
   {
      Fqn fqn = (Fqn) args[0];
      Object key = args[1];
      WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, false, false);

      if (workspaceNode == null)
      {
         if (trace) log.debug("Unable to find node " + fqn + " in workspace.");
         return null;
      }
      else
      {
         //add this node into the wrokspace
         notifier.notifyNodeVisited(fqn, true, ctx);
         Object val = workspaceNode.get(key);
         workspace.addNode(workspaceNode);
         notifier.notifyNodeVisited(fqn, false, ctx);
         return val;
      }
   }

   private Object getNodeAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
   {
      Fqn fqn = (Fqn) args[0];

      WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, false, false);

      if (workspaceNode == null)
      {
         if (trace) log.trace("Unable to find node " + fqn + " in workspace.");
         return null;
      }
      else if (workspaceNode.isDeleted())
      {
         if (trace) log.trace("Attempted to retrieve node " + fqn + " but it has been deleted!");
         return null;
      }
      else
      {
         notifier.notifyNodeVisited(fqn, true, ctx);
         workspace.addNode(workspaceNode);
         notifier.notifyNodeVisited(fqn, false, ctx);
         return workspaceNode.getNode();
      }
   }

   private Object getKeysAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
   {
      Fqn fqn = (Fqn) args[0];

      WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, false, false);

      if (workspaceNode == null)
      {
         if (trace) log.trace("unable to find node " + fqn + " in workspace.");
         return null;
      }
      else
      {
         notifier.notifyNodeVisited(fqn, true, ctx);
         Object keySet = workspaceNode.getKeys();
         workspace.addNode(workspaceNode);
         notifier.notifyNodeVisited(fqn, false, ctx);
         return keySet;
      }
   }

   private Object getDataAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
   {
      Fqn fqn = (Fqn) args[0];

      WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, false, false);

      if (workspaceNode == null)
      {
         if (trace) log.trace("unable to find node " + fqn + " in workspace.");
         return null;
      }
      else
      {
         notifier.notifyNodeVisited(fqn, true, ctx);
         Object data = workspaceNode.getData();
         workspace.addNode(workspaceNode);
         notifier.notifyNodeVisited(fqn, false, ctx);
         return data;
      }
   }

   private Object getChildNamesAndNotify(Object[] args, TransactionWorkspace workspace, InvocationContext ctx)
   {
      Fqn fqn = (Fqn) args[0];

      WorkspaceNode workspaceNode = fetchWorkspaceNode(ctx, fqn, workspace, false, false);

      if (workspaceNode == null)
      {
         if (trace) log.trace("Unable to find node " + fqn + " in workspace.");
         return null;
      }
      else
      {
         notifier.notifyNodeVisited(fqn, true, ctx);
         Object nameSet = workspaceNode.getChildrenNames();
         workspace.addNode(workspaceNode);
         notifier.notifyNodeVisited(fqn, false, ctx);
         return nameSet;
      }
   }

   // -----------------------------------------------------------------

   // Methods to help retrieval of nodes from the transaction workspace.

   // -----------------------------------------------------------------

   /**
    * Retrieves a node for a given Fqn from the workspace.  If the node does not exist in the workspace it is retrieved
    * from the cache's data structure.  Note that at no point is a NEW node created in the underlying data structure.
    * That is up to the {@link org.jboss.cache.interceptors.OptimisticCreateIfNotExistsInterceptor}.
    * <p/>
    * If the node requested is in the workspace but marked as deleted, this method will NOT retrieve it, unless undeleteIfNecessary
    * is true, in which case the node's <tt>deleted</tt> property is set to false first before being retrieved.
    *
    * @param fqn                 Fqn of the node to retrieve
    * @param workspace           transaction workspace to look in
    * @param undeleteIfNecessary if the node is in the workspace but marked as deleted, this meth
    * @param includeInvalidNodes
    * @return a node, if found, or null if not.
    */
   private WorkspaceNode fetchWorkspaceNode(InvocationContext ctx, Fqn fqn, TransactionWorkspace workspace, boolean undeleteIfNecessary, boolean includeInvalidNodes)
   {
      WorkspaceNode workspaceNode = workspace.getNode(fqn);
      // if we do not have the node then we need to add it to the workspace
      if (workspaceNode == null)
      {
         NodeSPI node = peekNode(ctx, fqn, false, true, includeInvalidNodes);
         if (node == null) return null;
         GlobalTransaction gtx = ctx.getGlobalTransaction();
         workspaceNode = lockAndCreateWorkspaceNode(nodeFactory, node, workspace, gtx, lockAcquisitionTimeout);

         // and add the node to the workspace.
         workspace.addNode(workspaceNode);
      }

      // Check that the workspace node has been marked as deleted.
      if (workspaceNode.isDeleted())
      {
         if (trace) log.trace("Node " + fqn + " has been deleted in the workspace.");
         if (undeleteIfNecessary)
         {
            undeleteWorkspaceNode(workspaceNode, fetchWorkspaceNode(ctx, fqn.getParent(), workspace, undeleteIfNecessary, includeInvalidNodes));
         }
         else if (!includeInvalidNodes)
         {
            // don't return deleted nodes if undeleteIfNecessary is false!
            workspaceNode = null;
         }
      }

      // set implicit node versioning flag.
      if (workspaceNode != null && !(workspaceNode.getVersion() instanceof DefaultDataVersion))
      {
         workspaceNode.setVersioningImplicit(false);
      }

      // now make sure all parents are in the wsp as well
      if (workspaceNode != null && !fqn.isRoot())
         fetchWorkspaceNode(ctx, fqn.getParent(), workspace, false, includeInvalidNodes);

      return workspaceNode;
   }
}
TOP

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

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.