AbstractCircuitBreaker.java

package net.secodo.jcircuitbreaker.breaker.impl;

import java.util.concurrent.ConcurrentHashMap;

import net.secodo.jcircuitbreaker.breaker.CircuitBreaker;
import net.secodo.jcircuitbreaker.breaker.ContextAwareCircuitBreaker;
import net.secodo.jcircuitbreaker.breaker.execution.ExecutedTask;
import net.secodo.jcircuitbreaker.breaker.execution.ExecutionContext;
import net.secodo.jcircuitbreaker.breaker.execution.impl.DefaultExecutionContextImpl;
import net.secodo.jcircuitbreaker.breakhandler.BreakHandler;
import net.secodo.jcircuitbreaker.breakhandler.exception.BreakHandlerException;
import net.secodo.jcircuitbreaker.breakstrategy.BreakStrategy;
import net.secodo.jcircuitbreaker.exception.CircuitBreakerException;
import net.secodo.jcircuitbreaker.exception.TaskExecutionException;
import net.secodo.jcircuitbreaker.task.Task;
import net.secodo.jcircuitbreaker.util.TimeUtil;


public abstract class AbstractCircuitBreaker<R> implements CircuitBreaker<R>, ContextAwareCircuitBreaker<R> {
  private final ConcurrentHashMap<String, ExecutedTask<R>> tasksInProgress;
  private final TimeUtil timeUtil;

  public AbstractCircuitBreaker() {
    this.tasksInProgress = new ConcurrentHashMap<>();
    timeUtil = new TimeUtil();
  }

  @Override
  public <U> R execute(Task<R> task, BreakStrategy<R> breakStrategy, BreakHandler<R> breakHandler,
                       U userData) throws TaskExecutionException, BreakHandlerException, CircuitBreakerException {
    DefaultExecutionContextImpl<R> executionContext = new DefaultExecutionContextImpl<>(tasksInProgress, userData);

    return executeInContext(task, breakStrategy, breakHandler, executionContext);
  }

  @Override
  public R executeInContext(Task<R> task, BreakStrategy<R> breakStrategy, BreakHandler<R> breakHandler,  
                            ExecutionContext<R> executionContext) 
      throws TaskExecutionException, BreakHandlerException, CircuitBreakerException {
    
    if (!breakStrategy.shouldBreak(task, executionContext)) {
      // track state asynchronously - not to block by resize operation on possible resize of hashmap

      R returnedValue = null;
      String taskKey = "not_existing_task_key"; // prevents possible null pointer in finally clause

      try {
        taskKey = String.valueOf(Thread.currentThread().getId()) + "_" + task.hashCode();

        final ExecutedTask<R> executedTask = new ExecutedTask<R>(task, timeUtil.getCurrentTimeMilis());
        tasksInProgress.put(taskKey, executedTask);

        try {
          returnedValue = task.execute();
        } catch (Exception ex) {
          throw new TaskExecutionException("Execution of task: " + task + " failed with exception: " + ex.getMessage(),
            ex);
        }

      } catch (TaskExecutionException ex) {
        throw ex;

      } catch (Exception ex) {
        String taskInfo;
        try {
          taskInfo = task.toString();
        } catch (Throwable th) {
          taskInfo = "[exception while call task.toString(): " + ex.getMessage() + "]";
        }

        throw new CircuitBreakerException("Exception while executing an actual call to task: " + taskInfo, ex);

      } finally {
        tasksInProgress.remove(taskKey);
      }


      return returnedValue;

    } else {
      return breakHandler.onBreak(this, task, breakStrategy, executionContext);
    }

  }

}