StatefulRetryHandler.java

package net.secodo.jcircuitbreaker.breakhandler.impl;

import java.util.Optional;
import java.util.concurrent.Callable;

import net.secodo.jcircuitbreaker.breaker.ContextAwareCircuitBreaker;
import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
import net.secodo.jcircuitbreaker.breakhandler.BreakHandler;
import net.secodo.jcircuitbreaker.breakhandler.exception.BreakHandlerException;
import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
import net.secodo.jcircuitbreaker.task.Task;


/**
 * An implementation of {@link BreakHandler} that retries to execute given <i>Task</i> again. The maximum number of
 * attempts is given as a constructor param. In case maximum number of attempts is exceeded
 * {@link RetryHandlerException} is thrown.
 *
 * <p>If you need other result of exceeding max number of attempts than just an exception, you can subclass this class,
 * call super, catch {@link RetryHandlerException} and handle this situation in the catch clause.
 *
 * <p>WARNING: This implementation is stateful and if directly passed to circuit breaker it will be shared between
 * different executions of circuit breaker. This is usually not what is expected because it stores number of
 * attempts in private field. If you plan to use {@link StatefulRetryHandler} in most cases you want to use
 * {@link ReusableRetryHandler} instead. This factory
 * create new instance of {@link StatefulRetryHandler} per single execution if circuit breaker.
 *
 * @param <R> the return type of real-method
 */
public class StatefulRetryHandler<R> implements BreakHandler<R> {
  private final int maxNumberOfAttempts;
  private final Optional<RetryHandlerOnRetryCallback<R>> onRetryCallback;

  private int currentRetryAttempt;

  public StatefulRetryHandler() {
    this(1);
  }

  public StatefulRetryHandler(int maxNumberOfAttempts) {
    this(maxNumberOfAttempts, null);
  }

  public StatefulRetryHandler(int maxNumberOfAttempts, RetryHandlerOnRetryCallback<R> onRetryCallback) {
    this.maxNumberOfAttempts = maxNumberOfAttempts;
    this.onRetryCallback = Optional.ofNullable(onRetryCallback);
  }

  @Override
  public R onBreak(ContextAwareCircuitBreaker<R> circuitBreaker, Task<R> task, BreakStrategy<R> breakStrategy,
                   ExecutionContext<R> executionContext) throws TaskExecutionException, BreakHandlerException {
    if (currentRetryAttempt == maxNumberOfAttempts) {
      throw new RetryHandlerException(
        "Number of retries reached maximum. Unable to execute the task: " + task + ". Max number of attempts was: " +
        maxNumberOfAttempts);
    }
    currentRetryAttempt++;
    onRetry(currentRetryAttempt, task, executionContext);

    return circuitBreaker.executeInContext(task, breakStrategy, this, executionContext);

  }

  protected void onRetry(int currentRetryAttempt, Task<R> task, ExecutionContext<R> executionContext) {
    if (onRetryCallback.isPresent()) {
      onRetryCallback.get().onRetry(currentRetryAttempt, executionContext);
    }
  }

}