Package org.jboss.cache.interceptors

Source Code of org.jboss.cache.interceptors.DataGravitatorInterceptor$ResponseValidityFilter

/*
* 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 org.jboss.cache.buddyreplication.BuddyManager;
import org.jboss.cache.buddyreplication.GravitateResult;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.marshall.MethodCallFactory;
import org.jboss.cache.marshall.MethodDeclarations;
import org.jboss.cache.marshall.NodeData;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.TransactionEntry;
import org.jgroups.Address;
import org.jgroups.blocks.GroupRequest;
import org.jgroups.blocks.RspFilter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* The Data Gravitator interceptor intercepts cache misses and attempts to
* gravitate data from other parts of the cluster.
* <p/>
* Only used if Buddy Replication is enabled.  Also, the interceptor only kicks
* in if an {@link org.jboss.cache.config.Option} is passed in to force Data
* Gravitation for a specific invocation or if <b>autoDataGravitation</b> is
* set to <b>true</b> when configuring Buddy Replication.
* <p/>
* See the JBoss Cache User Guide for more details on configuration options.
* There is a section dedicated to Buddy Replication in the Replication
* chapter.
*
* @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
*/
public class DataGravitatorInterceptor extends BaseRpcInterceptor
{
   private BuddyManager buddyManager;
   private boolean syncCommunications = false;
   private Map<GlobalTransaction, MethodCall> transactionMods = new ConcurrentHashMap<GlobalTransaction, MethodCall>();

   @Override
   public void setCache(CacheSPI cache)
   {
      super.setCache(cache);
      this.buddyManager = cache.getBuddyManager();
      syncCommunications = configuration.getCacheMode() == Configuration.CacheMode.REPL_SYNC || configuration.getCacheMode() == Configuration.CacheMode.INVALIDATION_SYNC;
   }

   public DataGravitatorInterceptor()
   {
      initLogger();
   }

   @Override
   protected boolean skipMethodCall(InvocationContext ctx)
   {
      return MethodDeclarations.isBlockUnblockMethod(ctx.getMethodCall().getMethodId()) ||
            ctx.getOptionOverrides().isSkipDataGravitation();
   }

   @Override
   protected Object handleGetChildrenNamesMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handleGetDataMapMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handleExistsMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handleGetKeysMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handleGetKeyValueMethod(InvocationContext ctx, Fqn fqn, Object key, boolean sendNodeEvent) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handleGetNodeMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      return handleGetMethod(ctx, fqn);
   }

   @Override
   protected Object handlePrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modification, Address coordinator, boolean onePhaseCommit) throws Throwable
   {
      try
      {
         Object returnValue = nextInterceptor(ctx);
         doPrepare(ctx.getGlobalTransaction(), ctx);
         return returnValue;
      }
      catch (Throwable throwable)
      {
         transactionMods.remove(ctx.getGlobalTransaction());
         throw throwable;

      }
   }

   @Override
   protected Object handleOptimisticPrepareMethod(InvocationContext ctx, GlobalTransaction gtx, List modifications, Map data, Address address, boolean onePhaseCommit) throws Throwable
   {
      return handlePrepareMethod(ctx, gtx, modifications, address, onePhaseCommit);
   }

   @Override
   protected Object handleRollbackMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      try
      {
         transactionMods.remove(ctx.getGlobalTransaction());
         return nextInterceptor(ctx);
      }
      catch (Throwable throwable)
      {
         transactionMods.remove(ctx.getGlobalTransaction());
         throw throwable;
      }
   }

   @Override
   protected Object handleCommitMethod(InvocationContext ctx, GlobalTransaction globalTransaction) throws Throwable
   {
      try
      {
         doCommit(ctx.getGlobalTransaction(), ctx);
         transactionMods.remove(ctx.getGlobalTransaction());
         return nextInterceptor(ctx);
      }
      catch (Throwable throwable)
      {
         transactionMods.remove(ctx.getGlobalTransaction());
         throw throwable;
      }
   }

   private Object handleGetMethod(InvocationContext ctx, Fqn fqn) throws Throwable
   {
      if (isGravitationEnabled(ctx))
      {
         // test that the Fqn being requested exists locally in the cache.
         if (trace) log.trace("Checking local existence of fqn " + fqn);
         if (BuddyManager.isBackupFqn(fqn))
         {
            log.info("Is call for a backup Fqn, not performing any gravitation.  Direct calls on internal backup nodes are *not* supported.");
         }
         else
         {
            if (peekNode(ctx, fqn, false, false, false) == null)
            {
               if (trace) log.trace("Gravitating from local backup tree");
               BackupData data = localBackupGet(fqn, ctx);

               if (data == null)
               {
                  if (trace) log.trace("Gravitating from remote backup tree");
                  // gravitate remotely.
                  data = remoteBackupGet(fqn);
               }

               if (data != null)
               {
                  // create node locally so I don't gravitate again
                  // when I do the put() call to the cluster!
                  //createNode(data.backupData, true);
                  // Make sure I replicate to my buddies.
                  if (trace)
                     log.trace("Passing the put call locally to make sure state is persisted and ownership is correctly established.");
                  createNode(ctx, data.backupData, false);

                  // very strange, the invocation contexts get twisted up here, and will need preservation.
                  // a bit crappy and hacky, all will be solved when we move to JBoss AOP in 2.1.0
                  //ctx.setMethodCall(m);

                  // Clean up the other nodes
                  cleanBackupData(data, ctx.getGlobalTransaction(), ctx);
               }
            }
            else
            {
               if (trace) log.trace("No need to gravitate; have this already.");
            }
         }
      }
      else
      {
         if (trace)
         {
            log.trace("Suppressing data gravitation for this call.");
         }
      }
      return nextInterceptor(ctx);
   }

   private boolean isGravitationEnabled(InvocationContext ctx)
   {
      boolean enabled = ctx.isOriginLocal();
      if (enabled)
      {
         if (!buddyManager.isAutoDataGravitation())
         {
            enabled = ctx.getOptionOverrides().getForceDataGravitation();
         }
      }
      return enabled;
   }

   private void doPrepare(GlobalTransaction gtx, InvocationContext ctx) throws Throwable
   {
      MethodCall cleanup = transactionMods.get(gtx);
      if (trace) log.trace("Broadcasting prepare for cleanup ops " + cleanup);
      if (cleanup != null)
      {
         MethodCall prepare;
         List<MethodCall> mods = new ArrayList<MethodCall>(1);
         mods.add(cleanup);
         if (configuration.isNodeLockingOptimistic())
         {
            prepare = MethodCallFactory.create(MethodDeclarations.optimisticPrepareMethod_id, gtx, mods, null, cache.getLocalAddress(), false);
         }
         else
         {
            prepare = MethodCallFactory.create(MethodDeclarations.prepareMethod_id, gtx, mods, cache.getLocalAddress(), syncCommunications);
         }

         replicateCall(ctx, getMembersOutsideBuddyGroup(), prepare, syncCommunications, ctx.getOptionOverrides());
      }
      else
      {
         if (trace) log.trace("Nothing to broadcast in prepare phase for gtx " + gtx);
      }
   }

   private void doCommit(GlobalTransaction gtx, InvocationContext ctx) throws Throwable
   {
      if (transactionMods.containsKey(gtx))
      {
         if (trace) log.trace("Broadcasting commit for gtx " + gtx);
         replicateCall(ctx, getMembersOutsideBuddyGroup(), MethodCallFactory.create(MethodDeclarations.commitMethod_id, gtx), syncCommunications, ctx.getOptionOverrides(), true, true);
      }
      else
      {
         if (trace) log.trace("Nothing to broadcast in commit phase for gtx " + gtx);
      }
   }

   private List<Address> getMembersOutsideBuddyGroup()
   {
      List<Address> members = new ArrayList<Address>(cache.getMembers());
      members.remove(cache.getLocalAddress());
      members.removeAll(buddyManager.getBuddyAddresses());
      return members;
   }

   private BackupData remoteBackupGet(Fqn name) throws Exception
   {

      BackupData result = null;

      GravitateResult gr = gravitateData(name);

      if (gr.isDataFound())
      {
         if (trace)
         {
            log.trace("Got response " + gr);
         }

         result = new BackupData(name, gr);
      }

      return result;
   }

   private void cleanBackupData(BackupData backup, GlobalTransaction gtx, InvocationContext ctx) throws Throwable
   {
      MethodCall cleanup = MethodCallFactory.create(MethodDeclarations.dataGravitationCleanupMethod_id, backup.primaryFqn, backup.backupFqn);


      if (trace) log.trace("Performing cleanup on [" + backup.primaryFqn + "]");
      if (gtx == null)
      {
         // broadcast removes
         // remove main Fqn
         if (trace) log.trace("Performing cleanup on [" + backup.backupFqn + "]");
         // remove backup Fqn
         replicateCall(ctx, cache.getMembers(), cleanup, syncCommunications, ctx.getOptionOverrides(), false, false);
      }
      else
      {
         if (trace)
         {
            log.trace("Data gravitation performed under global transaction " + gtx + ".  Not broadcasting cleanups until the tx commits.  Adding to tx mod list instead.");
         }
         transactionMods.put(gtx, cleanup);
         TransactionEntry te = getTransactionEntry(gtx);
         te.addModification(cleanup);
      }
   }

   private GravitateResult gravitateData(Fqn fqn) throws Exception
   {
      if (trace)
      {
         log.trace("cache=" + cache.getLocalAddress() + "; requesting data gravitation for Fqn " + fqn);
      }
      List<Address> mbrs = cache.getMembers();
      Boolean searchSubtrees = (buddyManager.isDataGravitationSearchBackupTrees() ? Boolean.TRUE : Boolean.FALSE);
      MethodCall dGrav = MethodCallFactory.create(MethodDeclarations.dataGravitationMethod_id, fqn, searchSubtrees);
      // doing a GET_ALL is crappy but necessary since JGroups' GET_FIRST could return null results from nodes that do
      // not have either the primary OR backup, and stop polling other valid nodes.
      List resps = cache.getRPCManager().callRemoteMethods(mbrs, dGrav, GroupRequest.GET_ALL, true, buddyManager.getBuddyCommunicationTimeout(), new ResponseValidityFilter(mbrs, cache.getLocalAddress()), false);
      if (trace)
      {
         log.trace("got responses " + resps);
      }
      if (resps == null)
      {
         if (mbrs.size() > 1) log.error("No replies to call " + dGrav);
         return GravitateResult.noDataFound();
      }

      // test for and remove exceptions
      GravitateResult result = GravitateResult.noDataFound();
      for (Object o : resps)
      {
         if (o instanceof Throwable)
         {
            if (log.isDebugEnabled())
            {
               log.debug("Found remote Throwable among responses - removing from responses list", (Exception) o);
            }
         }
         else if (o != null)
         {
            result = (GravitateResult) o;
            if (result.isDataFound())
            {
               break;
            }
         }
         else if (!configuration.isUseRegionBasedMarshalling())
         {
            // Null is OK if we are using region based marshalling; it
            // is what is returned if a region is inactive. Otherwise
            // getting a null is an error condition
            log.error("Unexpected null response to call " + dGrav + ".");
         }

      }

      return result;
   }

   @SuppressWarnings("unchecked")
   private void createNode(InvocationContext ctx, List<NodeData> nodeData, boolean localOnly) throws CacheException
   {
      for (NodeData data : nodeData)
      {
         if (localOnly)
         {
//            if (cache.peek(data.getFqn(), false) == null)
            if (peekNode(ctx, data.getFqn(), false, false, false) == null)
            {
               createNodesLocally(data.getFqn(), data.getAttributes());
            }
         }
         else
         {
            cache.put(data.getFqn(), data.getAttributes());
         }
      }
   }

   @SuppressWarnings("unchecked")
   private void createNodesLocally(Fqn<?> fqn, Map<?, ?> data) throws CacheException
   {
      int treeNodeSize;
      if ((treeNodeSize = fqn.size()) == 0) return;
      NodeSPI n = cache.getRoot();
      for (int i = 0; i < treeNodeSize; i++)
      {
         Object child_name = fqn.get(i);
         NodeSPI child_node = n.addChildDirect(new Fqn<Object>(child_name));
         if (child_node == null)
         {
            if (trace)
            {
               log.trace("failed to find or create child " + child_name + " of node " + n.getFqn());
            }
            return;
         }
         if (i == treeNodeSize - 1)
         {
            // set data
            child_node.putAllDirect(data);
         }
         n = child_node;
      }
   }

   private TransactionEntry getTransactionEntry(GlobalTransaction gtx)
   {
      return cache.getTransactionTable().get(gtx);
   }

   private BackupData localBackupGet(Fqn fqn, InvocationContext ctx) throws CacheException
   {
      GravitateResult result = cache.gravitateData(fqn, true);// a "local" gravitation
      boolean found = result.isDataFound();
      BackupData data = null;

      if (found)
      {
         Fqn backupFqn = result.getBuddyBackupFqn();
         data = new BackupData(fqn, result);
         // now the cleanup
         if (buddyManager.isDataGravitationRemoveOnFind())
         {
            // Remove locally only; the remote call will
            // be broadcast later
            ctx.getOptionOverrides().setCacheModeLocal(true);
            cache.removeNode(backupFqn);
         }
         else
         {
            cache.evict(backupFqn, true);
         }
      }

      return data;
   }

   private static class BackupData
   {
      Fqn primaryFqn;
      Fqn backupFqn;
      List<NodeData> backupData;

      public BackupData(Fqn primaryFqn, GravitateResult gr)
      {
         this.primaryFqn = primaryFqn;
         this.backupFqn = gr.getBuddyBackupFqn();
         this.backupData = gr.getNodeData();
      }
   }

   public static class ResponseValidityFilter implements RspFilter
   {
      private int numValidResponses = 0;
      private List<Address> pendingResponders;

      public ResponseValidityFilter(List<Address> expected, Address localAddress)
      {
         // so for now I used a list to keep it consistent
         this.pendingResponders = new ArrayList<Address>(expected);
         // We'll never get a response from ourself
         this.pendingResponders.remove(localAddress);
      }

      public boolean isAcceptable(Object object, Address address)
      {
         pendingResponders.remove(address);

         if (object instanceof GravitateResult)
         {
            GravitateResult response = (GravitateResult) object;
            if (response.isDataFound()) numValidResponses++;
         }
         // always return true to make sure a response is logged by the JGroups RpcDispatcher.
         return true;
      }

      public boolean needMoreResponses()
      {
         return numValidResponses < 1 && pendingResponders.size() > 0;
      }
   }
}
TOP

Related Classes of org.jboss.cache.interceptors.DataGravitatorInterceptor$ResponseValidityFilter

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.