private Object handleTopologyAffectedCommand(
      InvocationContext ctx, VisitableCommand command, Address origin, boolean sync)
      throws Throwable {
    log.tracef("handleTopologyAffectedCommand for command %s", command);

    if (isLocalOnly(ctx, command)) {
      return invokeNextInterceptor(ctx, command);
    }
    updateTopologyId((TopologyAffectedCommand) command);

    // TODO we may need to skip local invocation for read/write/tx commands if the command is too
    // old and none of its keys are local
    Object localResult = invokeNextInterceptor(ctx, command);

    boolean isNonTransactionalWrite = !ctx.isInTxScope() && command instanceof WriteCommand;
    boolean isTransactionalAndNotRolledBack = false;
    if (ctx.isInTxScope()) {
      isTransactionalAndNotRolledBack =
          !((TxInvocationContext) ctx).getCacheTransaction().isMarkedForRollback();
    }

    if (isNonTransactionalWrite || isTransactionalAndNotRolledBack) {
      stateTransferManager.forwardCommandIfNeeded(
          ((TopologyAffectedCommand) command), getAffectedKeys(ctx, command), origin, sync);
    }

    return localResult;
  }