]> git.basschouten.com Git - openhab-addons.git/blob
7058bb3860f41a9843496eac10dd96e74a9756d3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.util;
14
15 import java.util.concurrent.Callable;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.CompletionException;
18 import java.util.concurrent.Executor;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.TimeUnit;
21 import java.util.function.Supplier;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24
25 /**
26  * This is a utility class that allows adding {@link CompletableFuture} capabilities to a {@link Callable}.
27  * The provided callable will be executed asynchronously and the result will be used
28  * to complete the {@code RetryFuture} instance. As per its namesake, the RetryFuture allows
29  * the callable to reschedule itself by throwing a {@link RetryException}. Any other exception
30  * will simply complete the RetryFuture exceptionally as per {@link CompletableFuture#completeExceptionally(Throwable)}.
31  *
32  * @author Connor Petty - Initial contribution
33  *
34  */
35 @NonNullByDefault
36 public class RetryFuture<T> extends HeritableFuture<T> {
37
38     private final ScheduledExecutorService scheduler;
39
40     public RetryFuture(Callable<T> callable, ScheduledExecutorService scheduler, long delay, TimeUnit unit) {
41         this.scheduler = scheduler;
42         setParentFuture(() -> scheduler.schedule(new CallableTask(callable), delay, unit));
43     }
44
45     public RetryFuture(Supplier<CompletableFuture<T>> supplier, ScheduledExecutorService scheduler, long delay,
46             TimeUnit unit) {
47         this.scheduler = scheduler;
48         setParentFuture(() -> scheduler.schedule(new ComposeTask(supplier), delay, unit));
49     }
50
51     @Override
52     public Executor defaultExecutor() {
53         return scheduler;
54     }
55
56     private class CallableTask implements Runnable {
57
58         private final Callable<T> callable;
59
60         public CallableTask(Callable<T> callable) {
61             this.callable = callable;
62         }
63
64         @Override
65         public void run() {
66             try {
67                 complete(callable.call());
68             } catch (RetryException e) {
69                 setParentFuture(() -> {
70                     if (!isDone()) {
71                         return scheduler.schedule(this, e.delay, e.unit);
72                     }
73                     return null;
74                 });
75             } catch (Exception e) {
76                 completeExceptionally(e);
77             }
78         }
79     }
80
81     private class ComposeTask implements Runnable {
82
83         private final Supplier<CompletableFuture<T>> supplier;
84
85         public ComposeTask(Supplier<CompletableFuture<T>> supplier) {
86             this.supplier = supplier;
87         }
88
89         @Override
90         public void run() {
91             CompletableFuture<T> future = supplier.get();
92             setParentFuture(() -> future);
93             future.whenComplete((result, th) -> {
94                 if (th instanceof CompletionException) {
95                     th = th.getCause();
96                 }
97                 if (th instanceof RetryException) {
98                     RetryException e = (RetryException) th;
99                     setParentFuture(() -> {
100                         if (!isDone()) {
101                             return scheduler.schedule(this, e.delay, e.unit);
102                         }
103                         return null;
104                     });
105                 } else if (th != null) {
106                     completeExceptionally(th);
107                 } else {
108                     complete(result);
109                 }
110             });
111         }
112     }
113
114     /**
115      * This is a convinience method for calling {@code new RetryFuture<>(callable, scheduler)}
116      *
117      * @param <T> the result type of the callable task.
118      * @param callable the task to execute
119      * @param scheduler the scheduler to use
120      * @return a CompletableFuture that will return the result of the callable.
121      */
122     public static <T> CompletableFuture<T> callWithRetry(Callable<T> callable, ScheduledExecutorService scheduler) {
123         return new RetryFuture<>(callable, scheduler, 0, TimeUnit.NANOSECONDS);
124     }
125
126     public static <T> CompletableFuture<T> scheduleWithRetry(Callable<T> callable, ScheduledExecutorService scheduler,
127             long delay, TimeUnit unit) {
128         return new RetryFuture<>(callable, scheduler, delay, unit);
129     }
130
131     @SafeVarargs
132     public static <T> CompletableFuture<T> scheduleWithRetryForExceptions(Callable<T> callable,
133             ScheduledExecutorService scheduler, long initDelay, long retryDelay, TimeUnit unit,
134             Class<? extends Exception>... exceptions) {
135         Callable<T> task = () -> {
136             try {
137                 return callable.call();
138             } catch (RetryException ex) {
139                 throw ex;
140             } catch (Exception ex) {
141                 for (Class<? extends Exception> exClass : exceptions) {
142                     if (exClass.isInstance(ex)) {
143                         throw new RetryException(retryDelay, unit);
144                     }
145                 }
146                 throw ex;
147             }
148         };
149         return new RetryFuture<>(task, scheduler, initDelay, unit);
150     }
151
152     public static <T> CompletableFuture<T> composeWithRetry(Supplier<CompletableFuture<T>> supplier,
153             ScheduledExecutorService scheduler) {
154         return new RetryFuture<>(supplier, scheduler, 0, TimeUnit.NANOSECONDS);
155     }
156
157     public static <T> CompletableFuture<T> composeWithRetry(Supplier<CompletableFuture<T>> supplier,
158             ScheduledExecutorService scheduler, long initDelay, TimeUnit unit) {
159         return new RetryFuture<>(supplier, scheduler, initDelay, unit);
160     }
161 }