diff --git a/src/main/java/net/jodah/failsafe/Execution.java b/src/main/java/net/jodah/failsafe/Execution.java index 7be6fc19..87620a50 100644 --- a/src/main/java/net/jodah/failsafe/Execution.java +++ b/src/main/java/net/jodah/failsafe/Execution.java @@ -15,12 +15,12 @@ */ package net.jodah.failsafe; -import net.jodah.failsafe.internal.util.Assert; -import net.jodah.failsafe.internal.util.DelegatingScheduler; - import java.util.Arrays; import java.util.function.Supplier; +import net.jodah.failsafe.internal.util.Assert; +import net.jodah.failsafe.internal.util.DelegatingScheduler; + /** * Tracks executions and determines when an execution can be performed for a {@link RetryPolicy}. * @@ -131,4 +131,14 @@ ExecutionResult executeSync(Supplier supplier) { executor.handleComplete(result, this); return result; } + + ExecutionResultWithException executeSyncWithException(Supplier> supplier) { + for (PolicyExecutor> policyExecutor : policyExecutors) + supplier = policyExecutor.supplyWithException(supplier, scheduler); + + ExecutionResultWithException result = supplier.get(); + completed = result.isComplete(); + executor.handleComplete(result, this); + return result; + } } diff --git a/src/main/java/net/jodah/failsafe/ExecutionResult.java b/src/main/java/net/jodah/failsafe/ExecutionResult.java index b578b0eb..8e93d35d 100644 --- a/src/main/java/net/jodah/failsafe/ExecutionResult.java +++ b/src/main/java/net/jodah/failsafe/ExecutionResult.java @@ -50,7 +50,7 @@ public ExecutionResult(Object result, Throwable failure) { this(result, failure, false, 0, false, failure == null, failure == null); } - private ExecutionResult(Object result, Throwable failure, boolean nonResult, long waitNanos, boolean complete, + protected ExecutionResult(Object result, Throwable failure, boolean nonResult, long waitNanos, boolean complete, boolean success, Boolean successAll) { this.nonResult = nonResult; this.result = result; diff --git a/src/main/java/net/jodah/failsafe/ExecutionResultWithException.java b/src/main/java/net/jodah/failsafe/ExecutionResultWithException.java new file mode 100644 index 00000000..be88fd73 --- /dev/null +++ b/src/main/java/net/jodah/failsafe/ExecutionResultWithException.java @@ -0,0 +1,127 @@ +/* + * 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 net.jodah.failsafe; + +import java.util.Objects; + +/** + * The result of an execution. Immutable. + *

+ * Part of the Failsafe SPI. + * + * @author Jonathan Halterman + */ +public class ExecutionResultWithException extends ExecutionResult { + public ExecutionResultWithException(Object result, E failure) { + this(result, failure, false, 0, false, failure == null, failure == null); + } + + private ExecutionResultWithException(Object result, E failure, boolean nonResult, long waitNanos, boolean complete, + boolean success, Boolean successAll) { + super(result, failure, nonResult, waitNanos, complete, success, successAll); + } + + /** + * Returns a an ExecutionResult with the {@code result} set, {@code completed} true and {@code success} true. + */ + public static ExecutionResultWithException successWithException(Object result) { + return new ExecutionResultWithException<>(result, null); + } + + /** + * Returns a an ExecutionResult with the {@code failure} set, {@code completed} true and {@code success} false. + */ + public static ExecutionResultWithException failureWithException(E failure) { + return new ExecutionResultWithException<>(null, failure, false, 0, true, false, false); + } + + @SuppressWarnings("unchecked") + public E getFailure() { + return (E) super.getFailure(); + } + + /** + * Returns a copy of the ExecutionResult with a non-result, and completed and success set to true. Returns + * {@code this} if {@link #success} and {@link #result} are unchanged. + */ + ExecutionResultWithException withNonResult() { + return super.isSuccess() && super.getResult() == null && super.isNonResult() ? + this : + new ExecutionResultWithException(null, null, true, super.getWaitNanos(), true, true, super.getSuccessAll()); + } + + /** + * Returns a copy of the ExecutionResult with the {@code result} value, and completed and success set to true. Returns + * {@code this} if {@link #success} and {@link #result} are unchanged. + */ + public ExecutionResultWithException withResult(Object result) { + return super.isSuccess() && ((super.getResult() == null && result == null) || (super.getResult() != null && super.getResult().equals(result))) ? + this : + new ExecutionResultWithException<>(result, null, super.isNonResult(), super.getWaitNanos(), true, true, super.getSuccessAll()); + } + + /** + * Returns a copy of the ExecutionResult with the value set to true, else this if nothing has changed. + */ + @SuppressWarnings("unchecked") + public ExecutionResultWithException withComplete() { + return super.isComplete() ? this : new ExecutionResultWithException(super.getResult(), (E) super.getFailure(), super.isNonResult(), super.getWaitNanos(), true, super.isSuccess(), super.getSuccessAll()); + } + + /** + * Returns a copy of the ExecutionResult with the {@code completed} and {@code success} values. + */ + @SuppressWarnings("unchecked") + ExecutionResultWithException with(boolean completed, boolean success) { + return super.isComplete() == completed && super.isSuccess() == success ? + this : + new ExecutionResultWithException(super.getResult(), (E) super.getFailure(), super.isNonResult(), super.getWaitNanos(), completed, success, + super.isSuccess() && super.getSuccessAll()); + } + + /** + * Returns a copy of the ExecutionResult with the {@code waitNanos}, {@code completed} and {@code success} values. + */ + @SuppressWarnings("unchecked") + public ExecutionResultWithException with(long waitNanos, boolean completed, boolean success) { + return super.getWaitNanos() == waitNanos && super.isComplete() == completed && super.isSuccess() == success ? + this : + new ExecutionResultWithException(super.getResult(), (E) super.getFailure(), super.isNonResult(), waitNanos, completed, success, + super.isSuccess() && super.getSuccessAll()); + } + + @Override + public String toString() { + return "ExecutionResult[" + "result=" + super.getResult() + ", failure=" + super.getFailure() + ", nonResult=" + super.isNonResult() + + ", waitNanos=" + super.getWaitNanos() + ", complete=" + super.isComplete() + ", success=" + super.isSuccess() + ", successAll=" + super.getSuccessAll() + + ']'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ExecutionResultWithException that = (ExecutionResultWithException) o; + return Objects.equals(super.getResult(), that.getResult()) && Objects.equals(super.getFailure(), that.getFailure()); + } + + @Override + public int hashCode() { + return Objects.hash(super.getResult(), super.getFailure()); + } +} \ No newline at end of file diff --git a/src/main/java/net/jodah/failsafe/FailsafeExecutor.java b/src/main/java/net/jodah/failsafe/FailsafeExecutor.java index 0bd2beae..ed67fd40 100644 --- a/src/main/java/net/jodah/failsafe/FailsafeExecutor.java +++ b/src/main/java/net/jodah/failsafe/FailsafeExecutor.java @@ -15,18 +15,36 @@ */ package net.jodah.failsafe; -import net.jodah.failsafe.event.ExecutionCompletedEvent; -import net.jodah.failsafe.function.*; -import net.jodah.failsafe.internal.EventListener; -import net.jodah.failsafe.internal.util.Assert; -import net.jodah.failsafe.util.concurrent.Scheduler; +import static net.jodah.failsafe.Functions.getPromise; +import static net.jodah.failsafe.Functions.getPromiseExecution; +import static net.jodah.failsafe.Functions.getPromiseOfStage; +import static net.jodah.failsafe.Functions.getPromiseOfStageExecution; +import static net.jodah.failsafe.Functions.toAsyncSupplier; +import static net.jodah.failsafe.Functions.toCtxSupplier; +import static net.jodah.failsafe.Functions.toSupplier; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; import java.util.function.Supplier; -import static net.jodah.failsafe.Functions.*; +import net.jodah.failsafe.event.ExecutionCompletedEvent; +import net.jodah.failsafe.function.AsyncRunnable; +import net.jodah.failsafe.function.AsyncSupplier; +import net.jodah.failsafe.function.CheckedConsumer; +import net.jodah.failsafe.function.CheckedRunnable; +import net.jodah.failsafe.function.CheckedSupplier; +import net.jodah.failsafe.function.CheckedSupplierWithException; +import net.jodah.failsafe.function.ContextualRunnable; +import net.jodah.failsafe.function.ContextualSupplier; +import net.jodah.failsafe.internal.EventListener; +import net.jodah.failsafe.internal.util.Assert; +import net.jodah.failsafe.util.concurrent.Scheduler; /** *

@@ -67,6 +85,10 @@ public T get(CheckedSupplier supplier) { return call(execution -> Assert.notNull(supplier, "supplier")); } + public T getWithException(CheckedSupplierWithException supplier) throws E { + return callWithException(execution -> Assert.notNull(supplier, "supplier")); + } + /** * Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded. * @@ -385,6 +407,23 @@ private T call(Function> supplierFn) { return (T) result.getResult(); } + @SuppressWarnings("unchecked") + private T callWithException(Function> supplierFn) throws E { + Execution execution = new Execution(this); + Supplier> supplier = Functions.getWithException(supplierFn.apply(execution), execution); + + ExecutionResultWithException result = execution.executeSyncWithException(supplier); + Throwable failure = result.getFailure(); + if (failure != null) { + if (failure instanceof RuntimeException) + throw (RuntimeException) failure; + if (failure instanceof Error) + throw (Error) failure; + throw (E) failure; + } + return (T) result.getResult(); + } + /** * Calls the asynchronous {@code supplier} via the configured Scheduler, handling results according to the configured * policies. diff --git a/src/main/java/net/jodah/failsafe/Functions.java b/src/main/java/net/jodah/failsafe/Functions.java index c29fff85..85d523cf 100644 --- a/src/main/java/net/jodah/failsafe/Functions.java +++ b/src/main/java/net/jodah/failsafe/Functions.java @@ -66,6 +66,35 @@ else if (throwable instanceof InterruptedException) }; } + static Supplier> getWithException(CheckedSupplierWithException supplier, AbstractExecution execution) throws E { + return () -> { + ExecutionResultWithException result; + Throwable throwable = null; + try { + execution.preExecute(); + result = ExecutionResultWithException.successWithException(supplier.get()); + } catch (Throwable t) { + throwable = t; + // this probably needs some extra code in case an unexpected Exception is thrown + result = ExecutionResultWithException.failureWithException((E) t); + } finally { + // Guard against race with Timeout interruption + synchronized (execution) { + execution.canInterrupt = false; + if (execution.interrupted) + // Clear interrupt flag if interruption was intended + Thread.interrupted(); + else if (throwable instanceof InterruptedException) + // Set interrupt flag if interrupt occurred but was not intentional + Thread.currentThread().interrupt(); + } + } + + execution.record(result); + return result; + }; + } + /** * Returns a Supplier that pre-executes the {@code execution}, applies the {@code supplier}, records the result and * returns a promise containing the result. diff --git a/src/main/java/net/jodah/failsafe/PolicyExecutor.java b/src/main/java/net/jodah/failsafe/PolicyExecutor.java index 9901c24e..da5a9414 100644 --- a/src/main/java/net/jodah/failsafe/PolicyExecutor.java +++ b/src/main/java/net/jodah/failsafe/PolicyExecutor.java @@ -45,6 +45,10 @@ protected ExecutionResult preExecute() { return null; } + protected ExecutionResultWithException preExecuteWithException() { + return null; + } + /** * Performs an execution by calling pre-execute else calling the supplier and doing a post-execute. */ @@ -58,6 +62,16 @@ protected Supplier supply(Supplier supplier, S }; } + protected Supplier> supplyWithException(Supplier> supplier, Scheduler scheduler) { + return () -> { + ExecutionResultWithException result = preExecuteWithException(); + if (result != null) + return result; + + return postExecuteWithException(supplier.get()); + }; + } + /** * Performs synchronous post-execution handling for a {@code result}. */ @@ -74,6 +88,19 @@ protected ExecutionResult postExecute(ExecutionResult result) { return result; } + protected ExecutionResultWithException postExecuteWithException(ExecutionResultWithException result) { + if (isFailure(result)) { + result = onFailureWithException(result.with(false, false)); + callFailureListener(result); + } else { + result = result.with(true, true); + onSuccess(result); + callSuccessListener(result); + } + + return result; + } + /** * Performs an async execution by calling pre-execute else calling the supplier and doing a post-execute. */ @@ -134,6 +161,11 @@ protected ExecutionResult onFailure(ExecutionResult result) { return result; } + protected ExecutionResultWithException onFailureWithException(ExecutionResultWithException 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}. diff --git a/src/main/java/net/jodah/failsafe/function/CheckedSupplierWithException.java b/src/main/java/net/jodah/failsafe/function/CheckedSupplierWithException.java new file mode 100644 index 00000000..579f0897 --- /dev/null +++ b/src/main/java/net/jodah/failsafe/function/CheckedSupplierWithException.java @@ -0,0 +1,6 @@ +package net.jodah.failsafe.function; + +@FunctionalInterface +public interface CheckedSupplierWithException { + T get() throws E; +} \ No newline at end of file diff --git a/src/test/java/net/jodah/failsafe/functional/SupplierWithExceptionTest.java b/src/test/java/net/jodah/failsafe/functional/SupplierWithExceptionTest.java new file mode 100644 index 00000000..5eb05b97 --- /dev/null +++ b/src/test/java/net/jodah/failsafe/functional/SupplierWithExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 net.jodah.failsafe.functional; + +import java.io.IOException; + +import org.testng.annotations.Test; + +import net.jodah.failsafe.Failsafe; +import net.jodah.failsafe.RetryPolicy; + +@Test +public class SupplierWithExceptionTest { + @Test(expectedExceptions = IOException.class) + public void testCheckedException() throws IOException { + RetryPolicy retryPolicy = new RetryPolicy<>(); + + Failsafe.with(retryPolicy).getWithException(() -> { + throw new IOException(); + }); + } +} \ No newline at end of file