ExceptionThrowingHandler.java

package net.secodo.jcircuitbreaker.breakhandler.impl;

import java.lang.reflect.Constructor;

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.BreakHandlerFactory;
import net.secodo.jcircuitbreaker.breakhandler.exception.BreakHandlerException;
import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
import net.secodo.jcircuitbreaker.task.Task;


/**
 * The handler that throws custom exception in case "break" occurs.  The exception will be thrown with given
 * message. The exception MUST be subclass of {@link BreakHandlerException}.
 *
 * <p>NOTE: the exception class given as a parameter MUST define an exception with a publicly(!) accessible constructor
 * taking "message" String as the only parameter. This is because such constructor will be used to create instance of
 * exception to throw.
 *
 * <p>This implementation can be shared between different executions of circuit breaker and therefore does not require
 * factory method implementing {@link BreakHandlerFactory} to be reusable.
 *
 * @param <R> the return type of the original method that should have been executed (but break handler took over the
 *           control instead)
 */
public class ExceptionThrowingHandler<R> implements BreakHandler<R> {
  private final String exceptionMessage;
  private final Class<? extends BreakHandlerException> exceptionClass;
  private final Optional<ExceptionThrowingHandlerMessageCallback<R>> messageCallback;

  /**
   * Constructs new instance of {@link ExceptionThrowingHandler} which will throw {@link BreakHandlerException} with
   * given message in case "break" occurred (circuit breaker executed <i>break handler</i> instead of
   * <i>real-method</i>).
   *
   * @param exceptionMessage the message for the exception
   */
  public ExceptionThrowingHandler(String exceptionMessage) {
    this.exceptionClass = BreakHandlerException.class;
    this.exceptionMessage = exceptionMessage;
    this.messageCallback = Optional.empty();
  }

  /**
   * Constructs new instance of {@link ExceptionThrowingHandler} which will throw {@link BreakHandlerException} with
   * message obtained by calling
   * {@link ExceptionThrowingHandlerMessageCallback#buildMessage(Task, ExecutionContext)}.
   *
   * @param callback a callback method which returns the message for the exception
   */
  public ExceptionThrowingHandler(ExceptionThrowingHandlerMessageCallback<R> callback) {
    this.messageCallback = Optional.ofNullable(callback);
    this.exceptionClass = BreakHandlerException.class;
    this.exceptionMessage = "";
  }

  /**
   * Constructs new instance of {@link ExceptionThrowingHandler} which will throw exception of given class
   * with given message in case "break" occurred (circuit breaker executed <i>break handler</i> instead of
   * <i>real-method</i>).
   *
   * @param exceptionClass the type of exception to throw - must be subclass of {@link BreakHandlerException}
   * @param exceptionMessage the message for the exception
   */
  public ExceptionThrowingHandler(Class<? extends BreakHandlerException> exceptionClass, String exceptionMessage) {
    this.exceptionClass = exceptionClass;
    this.exceptionMessage = exceptionMessage;
    this.messageCallback = Optional.empty();
  }

  /**
   * Constructs new instance of {@link ExceptionThrowingHandler} which will throw exception of given class
   * with message obtained from given callback, in case "break" occurred (circuit breaker executed <i>break handler</i>
   * instead of <i>real-method</i>).
   *
   * @param exceptionClass the type of exception to throw - must be subclass of {@link BreakHandlerException}
   * @param callback a callback method which returns the message for the exception
   */
  public ExceptionThrowingHandler(Class<? extends BreakHandlerException> exceptionClass,
                                  ExceptionThrowingHandlerMessageCallback<R> callback) {
    this.exceptionClass = exceptionClass;
    this.exceptionMessage = "";
    this.messageCallback = Optional.of(callback);
  }

  @Override
  public R onBreak(ContextAwareCircuitBreaker<R> circuitBreaker, Task<R> task, BreakStrategy<R> breakStrategy,
                   ExecutionContext<R> executionContext) throws BreakHandlerException {
    final BreakHandlerException exceptionToThrow;
    String message = null;
    try {
      message = buildMessage(task, executionContext);

      final Constructor<? extends BreakHandlerException> exceptionClassConstructor = exceptionClass.getConstructor(
        String.class);

      exceptionToThrow = exceptionClassConstructor.newInstance(message);

    } catch (Exception ex) {
      throw new BreakHandlerException(
        "Unable to throw custom exception of class: " + exceptionClass + " and message: " + message,
        ex);
    }

    throw exceptionToThrow;
  }

  protected String buildMessage(Task<R> task, ExecutionContext<R> executionContext) {
    if (messageCallback.isPresent()) {
      return messageCallback.get().buildMessage(task, executionContext);
    } else {
      return this.exceptionMessage;
    }
  }
}