code: /*
   * Computoser is a music-composition algorithm and a website to present the results
   * Copyright (C) 2012-2014  Bozhidar Bozhanov
   *
   * Computoser is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * Computoser is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with Computoser.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.music.util;
  
  import java.util.concurrent.Callable;
  
  /**
   * Class that provides retrying functionality. Example:
   *
   * <code>
   * Callable<String> callable = new Callable<String>() {..};
   * String result = RetryableOperation.create(callable).retry(5, IOException.class);
   * </code>
   * @author bozho
   * @param <T> the return type of the operation
   */
  public class RetryableOperation<T> {
  
      private Callable<T> callable;
      private Runnable runnable;
      private boolean exponentialBackoff;
      private int backoffInterval = 500;
      private ExceptionHandler<? super Exception> exceptionHandler;
  
      /**
       * Create a retryable operation based on a Callable instance. The return
       * type of retry(..) is the type parameter of the Callable instance.
       *
       * @param callable
       * @return a new instance of RetryableOperation
       */
      public static <T> RetryableOperation<T> create(Callable<T> callable) {
          return new RetryableOperation<T>().withCallable(callable);
      }
  
      /**
       * Creates a retryable operation based on a Runnable instance. In this case
       * the retry(..) method always returns null.
       *
       * @param runnable
       * @return a new instance of RetryableOperation
       */
      public static RetryableOperation<?> create(Runnable runnable) {
          return new RetryableOperation<Object>().withRunnable(runnable);
      }
  
  
      /**
       * Retries the operation. Retrying happens regardless of the exception thrown.
       *
       * @param retries number of retries before the exception is thrown to the caller
       * @return the result of the operation (null if Runnable is used instead of Callable)
       * @throws Exception the exception that occurred on the last attempt
       */
      public <E extends Exception> T retry(int retries) throws Exception {
          return retry(retries, Exception.class);
      }
  
      /**
       * @param retries
       *            number of retries before the exception is thrown to the caller
       * @param retryForException
       *            retry only if the exception thrown by the operation is of this
       *            type. Otherwise rethrow the exception immediately, wrapped in
       *            a RuntimeException
       * @return the result of the operation (null if Runnable is used instead of
       *         Callable)
       * @throws E
       *             the exception that is expected to cause the operation to
       *             retry. It is only if the last retry attempt fails
       */
      public <E extends Exception> T retry(int retries, Class<E> retryForException) throws E {
          if (callable == null && runnable == null) {
              throw new IllegalStateException("Either runnable or callable must be set");
          }
          for (int i = 0; i < retries; i++) {
              try {
                  if (exponentialBackoff && i > 0) {
                      int sleepTime = (int) ((Math.pow(2, i) - 1) / 2) * backoffInterval;
                      Thread.sleep(sleepTime);
                  }
                  if (callable != null) {
                      return callable.call();
                  } else if (runnable != null) {
                      runnable.run();
                      return null;
                  }
              } catch (Exception e) {
  
                  // if there is a registered exception handler, perform the exception handling
                  // and optionally return, if the handler indicates that
                  if (exceptionHandler != null
                          && exceptionHandler.getExceptionClass().isInstance(e)
                          && !exceptionHandler.handle(exceptionHandler.getExceptionClass().cast(e))) {
                      return null;
                  }
                  // if the exception is the expected type, do nothing (i.e. retry the next iteration)
                  // if this is the last iteration, rethrow the exception
                  if (retryForException.isInstance(e)) {
                      E ex = retryForException.cast(e);
                      if (i == retries - 1) {
                          throw ex;
                      }
                  } else {
                      // if the exception is not the expected one, throw a runtime exception to the caller
                      throw new RuntimeException(e);
                  }
              }
          }
          // can't be reached - in case of failure on the last iteration the exception is rethrown
          return null;
      }
  
      private RetryableOperation<T> withCallable(Callable<T> callable) {
          this.callable = callable;
          return this;
      }
  
      private RetryableOperation<T> withRunnable(Runnable runnable) {
          this.runnable = runnable;
          return this;
      }
  
      /**
       * Sets an optional exception handler to this retryable operation. The
       * handler can choose whether to proceed with retrying or stop immediately.
       *
       * @param exceptionHandler
       * @return the RetryableOperation instance
       */
      @SuppressWarnings("unchecked")
      public RetryableOperation<T> withExceptionHandler(ExceptionHandler<? extends Exception> exceptionHandler) {
          this.exceptionHandler = (ExceptionHandler<? super Exception>) exceptionHandler;
          return this;
      }
  
      public RetryableOperation<T> withExponentialBackoff() {
          this.exponentialBackoff = true;
          return this;
      }
      public RetryableOperation<T> withExponentialBackoff(int backoffInterval) {
          this.exponentialBackoff = true;
          this.backoffInterval = backoffInterval;
          return this;
      }
      /**
       * Interface to be implemented whenever custom code is to be executed when an exception occurs while retrying
       * @author bozho
       *
       * @param <E> the type of the exception to be handled by this handler
       */
      public static interface ExceptionHandler<E extends Exception>  {
          /**
           *
           * @param ex
           * @return true if retrying should proceed; false if it should stop immediately
           */
          boolean handle(E ex);
  
          /**
           *
           * @return the exception class which this handler is capable of handling
           */
          Class<E> getExceptionClass();
      }
  }