@Override
 public void broadcastTopologyUpdate(
     String cacheName,
     CacheTopology cacheTopology,
     AvailabilityMode availabilityMode,
     boolean totalOrder,
     boolean distributed) {
   instance.broadcastTopologyUpdate(
       cacheName, cacheTopology, availabilityMode, totalOrder, distributed);
 }
    @Override
    public org.infinispan.topology.CacheStatusResponse handleJoin(
        String cacheName, Address joiner, CacheJoinInfo joinInfo, int viewId) throws Exception {
      CacheStatusResponse result = instance.handleJoin(cacheName, joiner, joinInfo, viewId);

      // Allow the joiner to receive some commands before the initial cache topology
      log.tracef("Delaying join response");
      Thread.sleep(500);
      return result;
    }
    @Override
    public void broadcastRebalanceStart(
        String cacheName, CacheTopology cacheTopology, boolean totalOrder, boolean distributed) {
      // Allow the joiner to receive some commands between the initial cache topology and the
      // rebalance start
      log.tracef("Delaying rebalance");
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }

      instance.broadcastRebalanceStart(cacheName, cacheTopology, totalOrder, distributed);
    }
  public void testReplace() throws Exception {
    cache(0).put("myKey", "myValue");

    // add an interceptor on second node that will block REPLACE commands right after
    // EntryWrappingInterceptor until we are ready
    final CountDownLatch replaceStartedLatch = new CountDownLatch(1);
    final CountDownLatch replaceProceedLatch = new CountDownLatch(1);
    boolean isVersioningEnabled = cache(0).getCacheConfiguration().versioning().enabled();
    cacheConfigBuilder
        .customInterceptors()
        .addInterceptor()
        .after(
            isVersioningEnabled
                ? VersionedEntryWrappingInterceptor.class
                : EntryWrappingInterceptor.class)
        .interceptor(
            new CommandInterceptor() {
              @Override
              protected Object handleDefault(InvocationContext ctx, VisitableCommand cmd)
                  throws Throwable {
                if (cmd instanceof ReplaceCommand) {
                  // signal we encounter a REPLACE
                  replaceStartedLatch.countDown();
                  // wait until it is ok to continue with REPLACE
                  if (!replaceProceedLatch.await(15, TimeUnit.SECONDS)) {
                    throw new TimeoutException();
                  }
                }
                return super.handleDefault(ctx, cmd);
              }
            });

    // do not allow coordinator to send topology updates to node B
    final ClusterTopologyManager ctm0 =
        TestingUtil.extractGlobalComponent(manager(0), ClusterTopologyManager.class);
    ctm0.setRebalancingEnabled(false);

    log.info("Adding a new node ..");
    addClusterEnabledCacheManager(cacheConfigBuilder);
    log.info("Added a new node");

    // node B is not a member yet and rebalance has not started yet
    CacheTopology cacheTopology =
        advancedCache(1).getComponentRegistry().getStateTransferManager().getCacheTopology();
    assertNull(cacheTopology.getPendingCH());
    assertTrue(cacheTopology.getMembers().contains(address(0)));
    assertFalse(cacheTopology.getMembers().contains(address(1)));
    assertFalse(cacheTopology.getCurrentCH().getMembers().contains(address(1)));

    // no keys should be present on node B yet because state transfer is blocked
    assertTrue(cache(1).keySet().isEmpty());

    // initiate a REPLACE
    Future<Object> getFuture =
        fork(
            new Callable<Object>() {
              @Override
              public Object call() throws Exception {
                try {
                  return cache(1).replace("myKey", "newValue");
                } catch (Exception e) {
                  log.errorf(e, "REPLACE failed: %s", e.getMessage());
                  throw e;
                }
              }
            });

    // wait for REPLACE command on node B to reach beyond *EntryWrappingInterceptor, where it will
    // block.
    // the value seen so far is null
    if (!replaceStartedLatch.await(15, TimeUnit.SECONDS)) {
      throw new TimeoutException();
    }

    // paranoia, yes the value is still missing from data container
    assertTrue(cache(1).keySet().isEmpty());

    // allow rebalance to start
    ctm0.setRebalancingEnabled(true);

    // wait for state transfer to end
    TestingUtil.waitForRehashToComplete(cache(0), cache(1));

    // the state should be already transferred now
    assertEquals(1, cache(1).keySet().size());

    // allow REPLACE to continue
    replaceProceedLatch.countDown();

    Object oldVal = getFuture.get(15, TimeUnit.SECONDS);
    assertNotNull(oldVal);
    assertEquals("myValue", oldVal);

    assertEquals("newValue", cache(0).get("myKey"));
    assertEquals("newValue", cache(1).get("myKey"));
  }
 @Override
 public void forceAvailabilityMode(String cacheName, AvailabilityMode availabilityMode) {
   instance.forceAvailabilityMode(cacheName, availabilityMode);
 }
 @Override
 public void forceRebalance(String cacheName) {
   instance.forceRebalance(cacheName);
 }
 @Override
 public void setRebalancingEnabled(boolean enabled) {
   instance.setRebalancingEnabled(enabled);
 }
 @Override
 public boolean isRebalancingEnabled() {
   return instance.isRebalancingEnabled();
 }
 @Override
 public void broadcastStableTopologyUpdate(
     String cacheName, CacheTopology cacheTopology, boolean totalOrder, boolean distributed) {
   instance.broadcastStableTopologyUpdate(cacheName, cacheTopology, totalOrder, distributed);
 }
 @Override
 public void handleClusterView(boolean isMerge, int viewId) {
   instance.handleClusterView(isMerge, viewId);
 }
 @Override
 public void handleRebalanceCompleted(
     String cacheName, Address node, int topologyId, Throwable throwable, int viewId)
     throws Exception {
   instance.handleRebalanceCompleted(cacheName, node, topologyId, throwable, viewId);
 }
 @Override
 public void handleLeave(String cacheName, Address leaver, int viewId) throws Exception {
   instance.handleLeave(cacheName, leaver, viewId);
 }