All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jacpfx.vertx.rest.response.basic.ResponseExecution Maven / Gradle / Ivy

/*
 * Copyright [2017] [Andy Moncsek]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jacpfx.vertx.rest.response.basic;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.shareddata.Counter;
import io.vertx.core.shareddata.Lock;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.jacpfx.common.ExecutionResult;
import org.jacpfx.common.VxmsShared;
import org.jacpfx.common.concurrent.LocalData;
import org.jacpfx.common.encoder.Encoder;
import org.jacpfx.common.throwable.ThrowableErrorConsumer;
import org.jacpfx.common.throwable.ThrowableFutureConsumer;

/**
 * Created by Andy Moncsek on 21.07.16. Contains the method chain to execute the response chaine
 * defined in the fluent API. The class is generic, so it is used for all types of response.
 */
public class ResponseExecution {

  private static final int DEFAULT_VALUE = 0;
  private static final long DEFAULT_LONG_VALUE = 0l;
  private static final int DEFAULT_LOCK_TIMEOUT = 2000;
  private static final long LOCK_VALUE = -1l;

  /**
   * Creates the response value based on the flow defined in the fluent API.  The resulting response
   * will be passed to an execution consumer.
   *
   * @param methodId, the method name/id to be executed
   * @param retry, the amount of retries
   * @param timeout, the timeout time for execution
   * @param circuitBreakerTimeout, the stateful circuit breaker release time
   * @param userOperation, the user operation to be executed (mapToStringResponse,
   * mapToByteResponse, mapToObjectResponse)
   * @param errorHandler, the intermediate error method, executed on each error
   * @param onFailureRespond, the method to be executed on failure
   * @param errorMethodHandler, the fallback method
   * @param vxmsShared the vxmsShared instance, containing the Vertx instance and other shared
   * objects per instance
   * @param fail, last thrown Exception
   * @param resultConsumer, the consumer of the {@link ExecutionResult}
   * @param  the type of response (String, byte, Object)
   */
  public static  void createResponse(String methodId,
      ThrowableFutureConsumer userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Throwable fail,
      Consumer> resultConsumer,
      int retry,
      long timeout,
      long circuitBreakerTimeout) {

    if (circuitBreakerTimeout > DEFAULT_LONG_VALUE) {
      executeStateful(methodId,
          retry, timeout,
          circuitBreakerTimeout,
          userOperation,
          errorHandler,
          onFailureRespond,
          errorMethodHandler,
          vxmsShared, fail, resultConsumer);
    } else {
      executeStateless(methodId,
          retry,
          timeout,
          circuitBreakerTimeout,
          userOperation,
          errorHandler,
          onFailureRespond,
          errorMethodHandler,
          vxmsShared, resultConsumer);
    }
  }

  private static  void executeStateless(String _methodId,
      int retry,
      long timeout,
      long release,
      ThrowableFutureConsumer _userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Consumer> resultConsumer) {
    final Future operationResult = Future.future();
    operationResult.setHandler(event -> {
      if (event.failed()) {
        int retryTemp = retry - 1;
        retryOrFail(_methodId,
            timeout,
            release,
            _userOperation,
            errorHandler,
            onFailureRespond,
            errorMethodHandler,
            vxmsShared, resultConsumer,
            event, retryTemp);
      } else {
        resultConsumer.accept(new ExecutionResult<>(event.result(), true, null));
      }
    });
    if (timeout > DEFAULT_LONG_VALUE) {
      addTimeoutHandler(timeout, vxmsShared.getVertx(), (l) -> {
        if (!operationResult.isComplete()) {
          operationResult.fail(new TimeoutException("operation timeout"));
        }
      });

    }
    executeAndCompleate(_userOperation, operationResult);


  }

  private static  void executeAndCompleate(ThrowableFutureConsumer userOperation,
      Future operationResult) {

    try {
      userOperation.accept(operationResult);
    } catch (Throwable throwable) {
      operationResult.fail(throwable);
    }
  }

  private static  void retryOrFail(String methodId,
      long timeout,
      long release,
      ThrowableFutureConsumer _userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Consumer> resultConsumer,
      AsyncResult event,
      int retryTemp) {
    if (retryTemp < DEFAULT_VALUE) {
      errorHandling(errorHandler, onFailureRespond, errorMethodHandler, resultConsumer, event);
    } else {
      retry(methodId,
          retryTemp,
          timeout,
          release,
          _userOperation,
          errorHandler,
          onFailureRespond,
          errorMethodHandler,
          vxmsShared,
          resultConsumer,
          event);
    }
  }

  private static  void executeStateful(String _methodId,
      int retry,
      long timeout,
      long circuitBreakerTimeout,
      ThrowableFutureConsumer _userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Throwable t,
      Consumer> resultConsumer) {
    final Future operationResult = Future.future();
    operationResult.setHandler(event -> {
      if (event.failed()) {
        statefulErrorHandling(_methodId,
            retry,
            timeout,
            circuitBreakerTimeout,
            _userOperation,
            errorHandler,
            onFailureRespond,
            errorMethodHandler,
            vxmsShared,
            resultConsumer,
            event);
      } else {
        resultConsumer.accept(new ExecutionResult<>(event.result(), true, null));
      }
    });

    executeLocked((lock, counter) ->
            counter.get(counterHandler -> {
              long currentVal = counterHandler.result();
              if (currentVal == DEFAULT_LONG_VALUE) {
                executeInitialState(retry,
                    timeout,
                    _userOperation,
                    vxmsShared,
                    operationResult,
                    lock,
                    counter);
              } else if (currentVal > 0) {
                executeDefaultState(timeout, _userOperation, vxmsShared, operationResult, lock);
              } else {
                releaseLockAndHandleError(errorHandler, onFailureRespond, errorMethodHandler,
                    resultConsumer, lock,
                    Optional.ofNullable(t).orElse(Future.failedFuture("circuit open").cause()));
              }
            }), _methodId, vxmsShared, errorHandler, onFailureRespond, errorMethodHandler,
        resultConsumer);


  }


  private static  void releaseLockAndHandleError(Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      Consumer> resultConsumer,
      Lock lock, Throwable cause) {
    Optional.ofNullable(lock).ifPresent(Lock::release);
    errorHandling(errorHandler, onFailureRespond, errorMethodHandler, resultConsumer,
        Future.failedFuture(cause));
  }

  private static  void executeDefaultState(long _timeout,
      ThrowableFutureConsumer _userOperation, VxmsShared vxmsShared, Future operationResult,
      Lock lock) {
    lock.release();
    if (_timeout > DEFAULT_LONG_VALUE) {
      addTimeoutHandler(_timeout, vxmsShared.getVertx(), (l) -> {
        if (!operationResult.isComplete()) {
          operationResult.fail(new TimeoutException("operation timeout"));
        }
      });
    }
    executeAndCompleate(_userOperation, operationResult);
  }

  private static  void executeInitialState(int retry,
      long timeout,
      ThrowableFutureConsumer _userOperation,
      VxmsShared vxmsShared,
      Future operationResult,
      Lock lock,
      Counter counter) {
    final long initialRetryCounterValue = (long) (retry + 1);
    counter.addAndGet(initialRetryCounterValue,
        rHandler -> executeDefaultState(timeout, _userOperation, vxmsShared, operationResult,
            lock));
  }

  private static  void statefulErrorHandling(String methodId,
      int retry,
      long timeout,
      long circuitBreakerTimeout,
      ThrowableFutureConsumer _userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Consumer> resultConsumer,
      AsyncResult event) {

    executeLocked((lock, counter) ->
            decrementAndExecute(counter, valHandler -> {
              if (valHandler.succeeded()) {
                handleStatefulError(methodId,
                    retry,
                    timeout,
                    circuitBreakerTimeout,
                    _userOperation,
                    errorHandler,
                    onFailureRespond,
                    errorMethodHandler,
                    vxmsShared,
                    resultConsumer,
                    event,
                    lock,
                    counter,
                    valHandler);
              } else {
                releaseLockAndHandleError(errorHandler, onFailureRespond, errorMethodHandler,
                    resultConsumer, lock, valHandler.cause());
              }
            }), methodId, vxmsShared, errorHandler, onFailureRespond, errorMethodHandler,
        resultConsumer);
  }

  private static void decrementAndExecute(Counter counter,
      Handler> asyncResultHandler) {
    counter.decrementAndGet(asyncResultHandler);
  }

  private static  void handleStatefulError(String methodId,
      int retry,
      long timeout,
      long circuitBreakerTimeout,
      ThrowableFutureConsumer _userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Consumer> resultConsumer,
      AsyncResult event, Lock lock, Counter counter,
      AsyncResult valHandler) {
    long count = valHandler.result();
    if (count <= DEFAULT_LONG_VALUE) {
      setCircuitBreakerReleaseTimer(retry, circuitBreakerTimeout, vxmsShared.getVertx(), counter);
      openCircuitBreakerAndHandleError(errorHandler, onFailureRespond, errorMethodHandler,
          resultConsumer, event, lock, counter);
    } else {
      lock.release();
      retry(methodId, retry, timeout, circuitBreakerTimeout, _userOperation, errorHandler,
          onFailureRespond, errorMethodHandler, vxmsShared, resultConsumer, event);
    }
  }


  private static  void openCircuitBreakerAndHandleError(Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      Consumer> resultConsumer,
      AsyncResult event, Lock lock, Counter counter) {
    counter.addAndGet(LOCK_VALUE, val -> {
      lock.release();
      errorHandling(errorHandler, onFailureRespond, errorMethodHandler, resultConsumer,
          Future.failedFuture(event.cause()));
    });
  }

  private static void setCircuitBreakerReleaseTimer(int _retry, long _release, Vertx vertx,
      Counter counter) {
    vertx.setTimer(_release,
        timer -> counter.addAndGet(Integer.valueOf(_retry + 1).longValue(), val -> {
        }));
  }


  private static void addTimeoutHandler(long _timeout, Vertx vertx, Handler longHandler) {
    vertx.setTimer(_timeout, longHandler);
  }

  private static  void errorHandling(Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      Consumer> resultConsumer,
      AsyncResult event) {
    try {
      final Future errorResult = Future.future();
      errorResult.setHandler(resultHandler -> {
        if (resultHandler.succeeded()) {
          resultConsumer.accept(new ExecutionResult<>(resultHandler.result(), true, true, null));
        } else {
          handleExecutionError(null, errorHandler, null, errorMethodHandler, resultHandler.cause());
        }
      });
      handleExecutionError(errorResult, errorHandler, onFailureRespond, errorMethodHandler,
          event.cause());

    } catch (Exception e) {
      resultConsumer.accept(new ExecutionResult<>(null, false, e));
    }
  }

  private static  void retry(String _methodId,
      int retryTemp,
      long timeout,
      long release,
      ThrowableFutureConsumer userOperation,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler,
      VxmsShared vxmsShared,
      Consumer> resultConsumer,
      AsyncResult event) {
    ResponseExecution.handleError(errorHandler, event.cause());
    createResponse(_methodId,
        userOperation,
        errorHandler,
        onFailureRespond,
        errorMethodHandler,
        vxmsShared, null,
        resultConsumer,
        retryTemp,
        timeout,
        release);
  }

  private static  void handleExecutionError(Future errorResult,
      Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond,
      Consumer errorMethodHandler, Throwable e) {
    ResponseExecution.handleError(errorHandler, e);
    try {
      if (onFailureRespond != null) {
        onFailureRespond.accept(e, errorResult);
      } else {
        errorMethodHandler.accept(e);
      }
    } catch (Throwable throwable) {
      errorResult.fail(throwable);
    }
  }


  /**
   * checks errorHandler for null and passes the throwable
   *
   * @param errorHandler The error handler that's accepts a Throwable
   * @param e, the Throwable
   */
  public static void handleError(Consumer errorHandler, Throwable e) {
    if (errorHandler != null) {
      errorHandler.accept(e);
    }

  }

  /**
   * Add a http content type to request header
   *
   * @param header the current header map
   * @param contentType the content type to add
   * @return as new header map instance
   */
  public static Map updateContentType(Map header,
      String contentType) {
    Map headerMap = new HashMap<>(header);
    headerMap.put("content-type", contentType);
    return headerMap;
  }

  /**
   * Checks the type of result and reply response
   *
   * @param val, the value to response
   * @param handler, the server response
   */
  public static void sendObjectResult(Object val, HttpServerResponse handler) {
    if (val instanceof String) {
      handler.end(String.valueOf(val));
    } else {
      handler.end(Buffer.buffer((byte[]) val));
    }
  }

  /**
   * Perform http status code and header update on http response
   *
   * @param headers the header to set
   * @param statusCode, the http code to set
   * @param response, the http response object
   */
  public static void updateHeaderAndStatuscode(Map headers, int statusCode,
      HttpServerResponse response) {
    updateResponseHaders(headers, response);
    updateResponseStatusCode(statusCode, response);
  }

  private static void updateResponseHaders(Map headers,
      HttpServerResponse response) {
    Optional.ofNullable(headers).ifPresent(
        h -> h.forEach(response::putHeader));
  }

  private static void updateResponseStatusCode(int httpStatusCode, HttpServerResponse response) {
    if (httpStatusCode != 0) {
      response.setStatusCode(httpStatusCode);
    }
  }

  /**
   * Apply the {@link Encoder} to a provided {@link Serializable}
   *
   * @param value, the value to encode
   * @param encoder the {@link Encoder}
   * @return an Optional with the encoded value
   */
  @SuppressWarnings("unchecked")
  public static Optional encode(Serializable value, Encoder encoder) {
    try {
      if (encoder instanceof Encoder.ByteEncoder) {
        return Optional.ofNullable(((Encoder.ByteEncoder) encoder).encode(value));
      } else if (encoder instanceof Encoder.StringEncoder) {
        return Optional.ofNullable(((Encoder.StringEncoder) encoder).encode(value));
      }

    } catch (Exception e) {
      e.printStackTrace();
    }

    return Optional.empty();
  }

  private static  void executeLocked(LockedConsumer consumer, String methodId,
      VxmsShared vxmsShared, Consumer errorHandler,
      ThrowableErrorConsumer onFailureRespond, Consumer errorMethodHandler,
      Consumer> resultConsumer) {
    // final SharedData sharedData = vxmsShared.getVertx().sharedData();
    // TODO make configurable if cluster wide lock is wanted... than use shared data object instead
    final LocalData localData = vxmsShared.getLocalData();
    localData.getLockWithTimeout(methodId, DEFAULT_LOCK_TIMEOUT, lockHandler -> {
      final Lock lock = lockHandler.result();
      if (lockHandler.succeeded()) {
        localData.getCounter(methodId, resultHandler -> {
          if (resultHandler.succeeded()) {
            consumer.execute(lock, resultHandler.result());
          } else {
            releaseLockAndHandleError(errorHandler, onFailureRespond, errorMethodHandler,
                resultConsumer, lock, resultHandler.cause());
          }
        });
      } else {
        releaseLockAndHandleError(errorHandler, onFailureRespond, errorMethodHandler,
            resultConsumer, lock, lockHandler.cause());
      }

    });
  }


  private interface LockedConsumer {

    void execute(Lock lock, Counter counter);
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy