protected <E extends Throwable> void rethrow(RetryContext context, String message) throws E {
   if (this.throwLastExceptionOnExhausted) {
     @SuppressWarnings("unchecked")
     E rethrow = (E) context.getLastThrowable();
     throw rethrow;
   } else {
     throw new ExhaustedRetryException(message, context.getLastThrowable());
   }
 }
 private RetryContext doOpenInternal(RetryPolicy retryPolicy, RetryState state) {
   RetryContext context = retryPolicy.open(RetrySynchronizationManager.getContext());
   if (state != null) {
     context.setAttribute(RetryContext.STATE_KEY, state.getKey());
   }
   if (context.hasAttribute(GLOBAL_STATE)) {
     registerContext(context, state);
   }
   return context;
 }
 /**
  * Clean up the cache if necessary and close the context provided (if the flag indicates that
  * processing was successful).
  *
  * @param retryPolicy the {@link RetryPolicy}
  * @param context the {@link RetryContext}
  * @param state the {@link RetryState}
  * @param succeeded whether the close succeeded
  */
 protected void close(
     RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) {
   if (state != null) {
     if (succeeded) {
       if (!context.hasAttribute(GLOBAL_STATE)) {
         this.retryContextCache.remove(state.getKey());
       }
       retryPolicy.close(context);
       context.setAttribute(RetryContext.CLOSED, true);
     }
   } else {
     retryPolicy.close(context);
     context.setAttribute(RetryContext.CLOSED, true);
   }
 }
 /**
  * Actions to take after final attempt has failed. If there is state clean up the cache. If there
  * is a recovery callback, execute that and return its result. Otherwise throw an exception.
  *
  * @param recoveryCallback the callback for recovery (might be null)
  * @param context the current retry context
  * @param state the {@link RetryState}
  * @param <T> the type to classify
  * @throws Exception if the callback does, and if there is no callback and the state is null then
  *     the last exception from the context
  * @throws ExhaustedRetryException if the state is not null and there is no recovery callback
  * @return T the payload to return
  */
 protected <T> T handleRetryExhausted(
     RecoveryCallback<T> recoveryCallback, RetryContext context, RetryState state)
     throws Throwable {
   context.setAttribute(RetryContext.EXHAUSTED, true);
   if (state != null && !context.hasAttribute(GLOBAL_STATE)) {
     this.retryContextCache.remove(state.getKey());
   }
   if (recoveryCallback != null) {
     context.setAttribute(RetryContext.RECOVERED, true);
     return recoveryCallback.recover(context);
   }
   if (state != null) {
     this.logger.debug("Retry exhausted after last attempt with no recovery path.");
     rethrow(context, "Retry exhausted after last attempt with no recovery path");
   }
   throw wrapIfNecessary(context.getLastThrowable());
 }
 @Override
 public Object doWithRetry(RetryContext context) throws Exception {
   context.setAttribute(RetryContext.NAME, label);
   try {
     return this.invocation.proceed();
   } catch (Exception e) {
     throw e;
   } catch (Error e) {
     throw e;
   } catch (Throwable e) {
     throw new IllegalStateException(e);
   }
 }
 private void registerContext(RetryContext context, RetryState state) {
   if (state != null) {
     Object key = state.getKey();
     if (key != null) {
       if (context.getRetryCount() > 1 && !this.retryContextCache.containsKey(key)) {
         throw new RetryException(
             "Inconsistent state for failed item key: cache key has changed. "
                 + "Consider whether equals() or hashCode() for the key might be inconsistent, "
                 + "or if you need to supply a better key");
       }
       this.retryContextCache.put(key, context);
     }
   }
 }
  /**
   * Delegate to the {@link RetryPolicy} having checked in the cache for an existing value if the
   * state is not null.
   *
   * @param state a {@link RetryState}
   * @param retryPolicy a {@link RetryPolicy} to delegate the context creation
   * @return a retry context, either a new one or the one used last time the same state was
   *     encountered
   */
  protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {

    if (state == null) {
      return doOpenInternal(retryPolicy);
    }

    Object key = state.getKey();
    if (state.isForceRefresh()) {
      return doOpenInternal(retryPolicy, state);
    }

    // If there is no cache hit we can avoid the possible expense of the
    // cache re-hydration.
    if (!this.retryContextCache.containsKey(key)) {
      // The cache is only used if there is a failure.
      return doOpenInternal(retryPolicy, state);
    }

    RetryContext context = this.retryContextCache.get(key);
    if (context == null) {
      if (this.retryContextCache.containsKey(key)) {
        throw new RetryException(
            "Inconsistent state for failed item: no history found. "
                + "Consider whether equals() or hashCode() for the item might be inconsistent, "
                + "or if you need to supply a better ItemKeyGenerator");
      }
      // The cache could have been expired in between calls to
      // containsKey(), so we have to live with this:
      return doOpenInternal(retryPolicy, state);
    }

    // Start with a clean slate for state that others may be inspecting
    context.removeAttribute(RetryContext.CLOSED);
    context.removeAttribute(RetryContext.EXHAUSTED);
    context.removeAttribute(RetryContext.RECOVERED);
    return context;
  }
 /**
  * Extension point for subclasses to decide on behaviour after catching an exception in a {@link
  * RetryCallback}. Normal stateless behaviour is not to rethrow, and if there is state we rethrow.
  *
  * @param retryPolicy the retry policy
  * @param context the current context
  * @param state the current retryState
  * @return true if the state is not null but subclasses might choose otherwise
  */
 protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, RetryState state) {
   return state != null && state.rollbackFor(context.getLastThrowable());
 }
  /**
   * Execute the callback once if the policy dictates that we can, otherwise execute the recovery
   * callback.
   *
   * @param recoveryCallback the {@link RecoveryCallback}
   * @param retryCallback the {@link RetryCallback}
   * @param state the {@link RetryState}
   * @param <T> the type of the return value
   * @param <E> the exception type to throw
   * @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
   * @throws ExhaustedRetryException if the retry has been exhausted.
   * @throws E an exception if the retry operation fails
   * @return T the retried value
   */
  protected <T, E extends Throwable> T doExecute(
      RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState state)
      throws E, ExhaustedRetryException {

    RetryPolicy retryPolicy = this.retryPolicy;
    BackOffPolicy backOffPolicy = this.backOffPolicy;

    // Allow the retry policy to initialise itself...
    RetryContext context = open(retryPolicy, state);
    if (this.logger.isTraceEnabled()) {
      this.logger.trace("RetryContext retrieved: " + context);
    }

    // Make sure the context is available globally for clients who need
    // it...
    RetrySynchronizationManager.register(context);

    Throwable lastException = null;

    boolean exhausted = false;
    try {

      // Give clients a chance to enhance the context...
      boolean running = doOpenInterceptors(retryCallback, context);

      if (!running) {
        throw new TerminatedRetryException(
            "Retry terminated abnormally by interceptor before first attempt");
      }

      // Get or Start the backoff context...
      BackOffContext backOffContext = null;
      Object resource = context.getAttribute("backOffContext");

      if (resource instanceof BackOffContext) {
        backOffContext = (BackOffContext) resource;
      }

      if (backOffContext == null) {
        backOffContext = backOffPolicy.start(context);
        if (backOffContext != null) {
          context.setAttribute("backOffContext", backOffContext);
        }
      }

      /*
       * We allow the whole loop to be skipped if the policy or context already
       * forbid the first try. This is used in the case of external retry to allow a
       * recovery in handleRetryExhausted without the callback processing (which
       * would throw an exception).
       */
      while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

        try {
          if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
          }
          // Reset the last exception, so if we are successful
          // the close interceptors will not think we failed...
          lastException = null;
          return retryCallback.doWithRetry(context);
        } catch (Throwable e) {

          lastException = e;

          try {
            registerThrowable(retryPolicy, state, context, e);
          } catch (Exception ex) {
            throw new TerminatedRetryException("Could not register throwable", ex);
          } finally {
            doOnErrorInterceptors(retryCallback, context, e);
          }

          if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
            try {
              backOffPolicy.backOff(backOffContext);
            } catch (BackOffInterruptedException ex) {
              lastException = e;
              // back off was prevented by another thread - fail the retry
              if (this.logger.isDebugEnabled()) {
                this.logger.debug(
                    "Abort retry because interrupted: count=" + context.getRetryCount());
              }
              throw ex;
            }
          }

          if (this.logger.isDebugEnabled()) {
            this.logger.debug("Checking for rethrow: count=" + context.getRetryCount());
          }

          if (shouldRethrow(retryPolicy, context, state)) {
            if (this.logger.isDebugEnabled()) {
              this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
            }
            throw RetryTemplate.<E>wrapIfNecessary(e);
          }
        }

        /*
         * A stateful attempt that can retry may rethrow the exception before now,
         * but if we get this far in a stateful retry there's a reason for it,
         * like a circuit breaker or a rollback classifier.
         */
        if (state != null && context.hasAttribute(GLOBAL_STATE)) {
          break;
        }
      }

      if (state == null && this.logger.isDebugEnabled()) {
        this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
      }

      exhausted = true;
      return handleRetryExhausted(recoveryCallback, context, state);

    } catch (Throwable e) {
      throw RetryTemplate.<E>wrapIfNecessary(e);
    } finally {
      close(retryPolicy, context, state, lastException == null || exhausted);
      doCloseInterceptors(retryCallback, context, lastException);
      RetrySynchronizationManager.clear();
    }
  }
 public Object recover(RetryContext context) {
   if (recoverer != null) {
     return recoverer.recover(args, context.getLastThrowable());
   }
   throw new ExhaustedRetryException("Retry was exhausted but there was no recovery path.");
 }
 @Override
 public Object recover(RetryContext context) {
   return this.recoverer.recover(this.args, context.getLastThrowable());
 }