]> git.basschouten.com Git - openhab-addons.git/blob
18e3703676c50a021c419c2604256c9145e49ad5
[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 e) {
98                     setParentFuture(() -> {
99                         if (!isDone()) {
100                             return scheduler.schedule(this, e.delay, e.unit);
101                         }
102                         return null;
103                     });
104                 } else if (th != null) {
105                     completeExceptionally(th);
106                 } else {
107                     complete(result);
108                 }
109             });
110         }
111     }
112
113     /**
114      * This is a convinience method for calling {@code new RetryFuture<>(callable, scheduler)}
115      *
116      * @param <T> the result type of the callable task.
117      * @param callable the task to execute
118      * @param scheduler the scheduler to use
119      * @return a CompletableFuture that will return the result of the callable.
120      */
121     public static <T> CompletableFuture<T> callWithRetry(Callable<T> callable, ScheduledExecutorService scheduler) {
122         return new RetryFuture<>(callable, scheduler, 0, TimeUnit.NANOSECONDS);
123     }
124
125     public static <T> CompletableFuture<T> scheduleWithRetry(Callable<T> callable, ScheduledExecutorService scheduler,
126             long delay, TimeUnit unit) {
127         return new RetryFuture<>(callable, scheduler, delay, unit);
128     }
129
130     @SafeVarargs
131     public static <T> CompletableFuture<T> scheduleWithRetryForExceptions(Callable<T> callable,
132             ScheduledExecutorService scheduler, long initDelay, long retryDelay, TimeUnit unit,
133             Class<? extends Exception>... exceptions) {
134         Callable<T> task = () -> {
135             try {
136                 return callable.call();
137             } catch (RetryException ex) {
138                 throw ex;
139             } catch (Exception ex) {
140                 for (Class<? extends Exception> exClass : exceptions) {
141                     if (exClass.isInstance(ex)) {
142                         throw new RetryException(retryDelay, unit);
143                     }
144                 }
145                 throw ex;
146             }
147         };
148         return new RetryFuture<>(task, scheduler, initDelay, unit);
149     }
150
151     public static <T> CompletableFuture<T> composeWithRetry(Supplier<CompletableFuture<T>> supplier,
152             ScheduledExecutorService scheduler) {
153         return new RetryFuture<>(supplier, scheduler, 0, TimeUnit.NANOSECONDS);
154     }
155
156     public static <T> CompletableFuture<T> composeWithRetry(Supplier<CompletableFuture<T>> supplier,
157             ScheduledExecutorService scheduler, long initDelay, TimeUnit unit) {
158         return new RetryFuture<>(supplier, scheduler, initDelay, unit);
159     }
160 }