public RetryInvocationHandler(
     FailoverProxyProvider proxyProvider, Map<String, RetryPolicy> methodNameToPolicyMap) {
   this.proxyProvider = proxyProvider;
   this.defaultPolicy = RetryPolicies.TRY_ONCE_THEN_FAIL;
   this.methodNameToPolicyMap = methodNameToPolicyMap;
   this.currentProxy = proxyProvider.getProxy();
 }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    RetryPolicy policy = methodNameToPolicyMap.get(method.getName());
    if (policy == null) {
      policy = defaultPolicy;
    }

    // The number of times this method invocation has been failed over.
    int invocationFailoverCount = 0;
    int retries = 0;
    while (true) {
      // The number of times this invocation handler has ever been failed over,
      // before this method invocation attempt. Used to prevent concurrent
      // failed method invocations from triggering multiple failover attempts.
      long invocationAttemptFailoverCount;
      synchronized (proxyProvider) {
        invocationAttemptFailoverCount = proxyProviderFailoverCount;
      }
      try {
        Object ret = invokeMethod(method, args);
        hasMadeASuccessfulCall = true;
        return ret;
      } catch (Exception e) {
        boolean isMethodIdempotent =
            proxyProvider
                .getInterface()
                .getMethod(method.getName(), method.getParameterTypes())
                .isAnnotationPresent(Idempotent.class);
        RetryAction action =
            policy.shouldRetry(e, retries++, invocationFailoverCount, isMethodIdempotent);
        if (action.action == RetryAction.RetryDecision.FAIL) {
          if (action.reason != null) {
            LOG.warn(
                "Exception while invoking "
                    + currentProxy.getClass()
                    + "."
                    + method.getName()
                    + ". Not retrying because "
                    + action.reason,
                e);
          }
          throw e;
        } else { // retry or failover
          // avoid logging the failover if this is the first call on this
          // proxy object, and we successfully achieve the failover without
          // any flip-flopping
          boolean worthLogging = !(invocationFailoverCount == 0 && !hasMadeASuccessfulCall);
          worthLogging |= LOG.isDebugEnabled();
          if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY && worthLogging) {
            String msg =
                "Exception while invoking "
                    + method.getName()
                    + " of class "
                    + currentProxy.getClass().getSimpleName();
            if (invocationFailoverCount > 0) {
              msg += " after " + invocationFailoverCount + " fail over attempts";
            }
            msg += ". Trying to fail over " + formatSleepMessage(action.delayMillis);
            if (LOG.isDebugEnabled()) {
              LOG.debug(msg, e);
            } else {
              LOG.warn(msg);
            }
          } else {
            if (LOG.isDebugEnabled()) {
              LOG.debug(
                  "Exception while invoking "
                      + method.getName()
                      + " of class "
                      + currentProxy.getClass().getSimpleName()
                      + ". Retrying "
                      + formatSleepMessage(action.delayMillis),
                  e);
            }
          }

          if (action.delayMillis > 0) {
            ThreadUtil.sleepAtLeastIgnoreInterrupts(action.delayMillis);
          }

          if (action.action == RetryAction.RetryDecision.FAILOVER_AND_RETRY) {
            // Make sure that concurrent failed method invocations only cause a
            // single actual fail over.
            synchronized (proxyProvider) {
              if (invocationAttemptFailoverCount == proxyProviderFailoverCount) {
                proxyProvider.performFailover(currentProxy);
                proxyProviderFailoverCount++;
                currentProxy = proxyProvider.getProxy();
              } else {
                LOG.warn(
                    "A failover has occurred since the start of this method"
                        + " invocation attempt.");
              }
            }
            invocationFailoverCount++;
          }
        }
      }
    }
  }
 public RetryInvocationHandler(FailoverProxyProvider proxyProvider, RetryPolicy retryPolicy) {
   this.proxyProvider = proxyProvider;
   this.defaultPolicy = retryPolicy;
   this.methodNameToPolicyMap = Collections.emptyMap();
   this.currentProxy = proxyProvider.getProxy();
 }
 @Override
 public void close() throws IOException {
   proxyProvider.close();
 }