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

dev.failsafe.spi.PolicyExecutor Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
/*
 * Copyright 2018 the original author or authors.
 *
 * 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 dev.failsafe.spi;

import dev.failsafe.internal.EventHandler;
import dev.failsafe.ExecutionContext;
import dev.failsafe.Policy;

import java.util.concurrent.CompletableFuture;
import java.util.function.Function;

/**
 * Handles execution and execution results according to a policy. May contain pre-execution and post-execution
 * behaviors. Each PolicyExecutor makes its own determination about whether an execution result is a success or
 * failure.
 *
 * @param  result type
 * @author Jonathan Halterman
 */
public abstract class PolicyExecutor {
  /** Index of the policy relative to other policies in a composition, inner-most first */
  private final int policyIndex;

  /** Optional APIs for policies that support them */
  private final FailurePolicy failurePolicy;
  private final EventHandler successHandler;
  private final EventHandler failureHandler;

  protected PolicyExecutor(Policy policy, int policyIndex) {
    this.policyIndex = policyIndex;
    this.failurePolicy = policy instanceof FailurePolicy ? (FailurePolicy) policy : null;
    this.successHandler = EventHandler.ofExecutionCompleted(policy.getConfig().getSuccessListener());
    this.failureHandler = EventHandler.ofExecutionCompleted(policy.getConfig().getFailureListener());
  }

  /**
   * Returns the index of the policy relative to other policies in a composition, where the inner-most policy in a
   * composition has an index of {@code 0}.
   */
  public int getPolicyIndex() {
    return policyIndex;
  }

  /**
   * Called before execution to return an alternative result or failure such as if execution is not allowed or needed.
   */
  protected ExecutionResult preExecute() {
    return null;
  }

  /**
   * Performs an execution by calling pre-execute else calling the supplier and doing a post-execute.
   */
  public Function, ExecutionResult> apply(
    Function, ExecutionResult> innerFn, Scheduler scheduler) {
    return execution -> {
      ExecutionResult result = preExecute();
      if (result != null) {
        // Still need to preExecute when returning an alternative result before making it to the terminal Supplier
        execution.preExecute();
        return result;
      }

      return postExecute(execution, innerFn.apply(execution));
    };
  }

  /**
   * Performs synchronous post-execution handling for a {@code result}.
   */
  public ExecutionResult postExecute(ExecutionInternal execution, ExecutionResult result) {
    execution.recordAttempt();
    if (isFailure(result)) {
      result = onFailure(execution, result.withFailure());
      handleFailure(result, execution);
    } else {
      result = result.withSuccess();
      onSuccess(result);
      handleSuccess(result, execution);
    }

    return result;
  }

  /**
   * Performs an async execution by calling pre-execute else calling the supplier and doing a post-execute. Implementors
   * must handle a null result from a supplier, which indicates that an async execution has occurred, that a result will
   * be recorded separately, and that postExecute handling should not be performed.
   */
  public Function, CompletableFuture>> applyAsync(
    Function, CompletableFuture>> innerFn, Scheduler scheduler,
    FailsafeFuture future) {

    return execution -> {
      ExecutionResult result = preExecute();
      if (result != null) {
        // Still need to preExecute when returning an alternative result before making it to the terminal Supplier
        execution.preExecute();
        return CompletableFuture.completedFuture(result);
      }

      return innerFn.apply(execution).thenCompose(r -> {
        return r == null ? ExecutionResult.nullFuture() : postExecuteAsync(execution, r, scheduler, future);
      });
    };
  }

  /**
   * Performs potentially asynchronous post-execution handling for a {@code result}.
   */
  protected synchronized CompletableFuture> postExecuteAsync(AsyncExecutionInternal execution,
    ExecutionResult result, Scheduler scheduler, FailsafeFuture future) {
    CompletableFuture> postFuture = null;

    /* Guard against post executing twice for the same execution. This will happen if one async execution result is
     * recorded by a timeout and another via AsyncExecution.record. */
    if (!execution.isAsyncExecution() || !execution.isPostExecuted(policyIndex)) {
      execution.recordAttempt();
      if (isFailure(result)) {
        postFuture = onFailureAsync(execution, result.withFailure(), scheduler, future).whenComplete(
          (postResult, error) -> handleFailure(postResult, execution));
      } else {
        result = result.withSuccess();
        onSuccess(result);
        handleSuccess(result, execution);
        postFuture = CompletableFuture.completedFuture(result);
      }

      if (execution.isAsyncExecution())
        execution.setPostExecuted(policyIndex);
    }
    return postFuture;
  }

  /**
   * Returns whether the {@code result} is a success according to the policy. If the {code result} has no result, it is
   * not a failure.
   */
  protected boolean isFailure(ExecutionResult result) {
    if (result.isNonResult())
      return false;
    else if (failurePolicy != null)
      return failurePolicy.isFailure(result.getResult(), result.getFailure());
    else
      return result.getFailure() != null;
  }

  /**
   * Performs post-execution handling for a {@code result} that is considered a success according to {@link
   * #isFailure(ExecutionResult)}.
   */
  protected void onSuccess(ExecutionResult result) {
  }

  /**
   * Performs post-execution handling for a {@code result} that is considered a failure according to {@link
   * #isFailure(ExecutionResult)}, possibly creating a new result, else returning the original {@code result}.
   */
  protected ExecutionResult onFailure(ExecutionContext context, ExecutionResult result) {
    return result;
  }

  /**
   * Performs potentially asynchrononus post-execution handling for a failed {@code result}, possibly creating a new
   * result, else returning the original {@code result}.
   */
  protected CompletableFuture> onFailureAsync(ExecutionContext context, ExecutionResult result,
    Scheduler scheduler, FailsafeFuture future) {
    try {
      return CompletableFuture.completedFuture(onFailure(context, result));
    } catch (Throwable t) {
      // Handle unexpected hard errors in user code
      CompletableFuture> r = new CompletableFuture<>();
      r.completeExceptionally(t);
      return r;
    }
  }

  private void handleSuccess(ExecutionResult result, ExecutionContext context) {
    if (successHandler != null && result.isComplete())
      successHandler.handle(result, context);
  }

  private void handleFailure(ExecutionResult result, ExecutionContext context) {
    if (failureHandler != null && result.isComplete())
      failureHandler.handle(result, context);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy