-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathAsyncManager.java
361 lines (321 loc) · 10.9 KB
/
AsyncManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package org.vaadin.flow.helper;
import com.vaadin.external.org.slf4j.LoggerFactory;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.UI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Helper class for executing asynchronous tasks in your views using push or dynamic polling.
* <p>
* Typically actions required deferred execution are computationally heavy, but the computation itself
* do not require {@code UI} lock. This helper allows to simplify deferred task creation:
* <p>
* <pre><code>
* AsyncManager.register(this, task -> {
* doSomeHeavylifting();
* task.push({
* updateView();
* });
* });
* </code></pre>
* <p>
* This will start a new thread and then, when the results of computation will be available, will push it
* to the client. If push is not enabled in your application, it will use polling instead. {@code AsyncManager} takes care
* of interrupting tasks when the view is detached from UI or if the UI leaves current view.
* <p>
* Initial configuration of AsyncManager can be done using {@link AsyncManager#setExceptionHandler(ExceptionHandler)},
* {@link AsyncManager#setPollingIntervals(int...)} and {@link AsyncManager#setExecutorService(ExecutorService)} static methods.
*
* @author Artem Godin
* @see AsyncTask
* @see Action
*/
public final class AsyncManager {
//--- Defaults
/**
* Default pool size (25 threads)
*/
private static final int DEFAULT_POOL_SIZE = 25;
/**
* Default polling intervals (200 ms)
*/
private static final int[] DEFAULT_POLLING_INTERVALS = {200};
//--- The one and only instance of AsyncManager
/**
* Instance of AsyncManager
*/
private static final AsyncManager instance = new AsyncManager();
//-- Private fields
/**
* Exception handler
*/
private ExceptionHandler exceptionHandler = AsyncManager::logException;
/**
* Task state handler
*/
private TaskStateHandler taskStateHandler;
/**
* Instance of {@link ExecutorService} used for asynchronous tasks
*/
private ExecutorService executorService = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE);
/**
* Polling intervals
*/
private int[] pollingIntervals = DEFAULT_POLLING_INTERVALS;
private AsyncManager() { // Not directly instantiatable
}
//--- Static methods
/**
* Get instance of AsyncManager
*
* @return Instance of AsyncManager
*/
public static AsyncManager getInstance() {
return instance;
}
/**
* Register and start a new deferred action. Action are started immediately in a separate thread and do not hold
* {@code UI} or {@code VaadinSession} locks.
* <p>
* Shorthand for {@code AsyncManager.getInstance().registerAsync(component, action)}
*
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link Task}, associated with this action
*/
public static Task register(Component component, Action action) {
return getInstance().registerAsync(component, action);
}
/**
* Register and runs eager action. Action are started immediately in the same thread.
* <p>
* Shorthand for {@code AsyncManager.getInstance().registerSync(component, action)}
*
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link Task}, associated with this action
*/
public static Task run(Component component, Action action) {
return getInstance().registerSync(component, action);
}
/**
* Default exception handler that simply logs the exception
*
* @param task Task where exception happened
* @param e Exception to handle
*/
private static void logException(Task task, Throwable e) {
LoggerFactory.getLogger(AsyncManager.class.getName()).warn(e.getMessage(), e);
}
//--- Getters and setters
/**
* Set custom exception handler for exceptions thrown in async tasks if you need custom logging or
* reporting
*
* @param handler Exception handler to set
*/
public void setExceptionHandler(ExceptionHandler handler) {
exceptionHandler = handler;
}
/**
* Set custom task state handler to monitor
* @param handler State handler to set
*/
public void setTaskStateHandler(TaskStateHandler handler) {
taskStateHandler = handler;
}
/**
* Get a {@link ExecutorService} used for asynchronous task execution.
*
* @return static instance of {@link ExecutorService}
*/
ExecutorService getExecutorService() {
return executorService;
}
/**
* Set {@link ExecutorService} to be used for asynchronous task execution.
*/
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
/**
* Get polling intervals
*
* @return polling intervals in milliseconds
*/
int[] getPollingIntervals() {
return pollingIntervals;
}
/**
* Set polling intervals for polling mode. When multiple values are specified, the
* resulting polling intervals will automatically adjust. For example,
* {@code setPollingIntervals(200, 200, 200, 200, 200, 500, 500, 1000)} will make the first
* 5 polls with 200 ms interval, the next 2 - with 500 ms and after than 1 sec intervals will be used.
* <p>
* This can be used to reduce amount of client requests when really computationally-heavy tasks
* are executed.
*
* @param milliseconds Polling intervals in milliseconds
*/
public void setPollingIntervals(int... milliseconds) {
if (milliseconds.length == 0) {
pollingIntervals = DEFAULT_POLLING_INTERVALS;
}
pollingIntervals = milliseconds;
}
/**
* Register and start a new deferred action. Action are started immediately in a separate thread and do not hold
* {@code UI} or {@code VaadinSession} locks.
*
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link Task}, associated with this action
*/
public Task registerSync(Component component, Action action) {
Objects.requireNonNull(component);
Task task = new SyncTask(this);
registerTask(component, task, action);
return task;
}
/**
* Register and start a new deferred action. Action are started immediately in a separate thread and do not hold
* {@code UI} or {@code VaadinSession} locks.
*
* @param component Component, where the action needs to be performed, typically your view
* @param action Action
* @return {@link Task}, associated with this action
*/
public Task registerAsync(Component component, Action action) {
Objects.requireNonNull(component);
Task task = new AsyncTask(this);
registerTask(component, task, action);
return task;
}
//--- Implementation
void registerTask(Component component, Task task, Action action) {
UI ui = component.getUI().orElse(null);
if (ui != null) {
task.register(ui, component, action);
} else {
component.addAttachListener(attachEvent -> {
attachEvent.unregisterListener();
task.register(attachEvent.getUI(), component, action);
});
}
}
/**
* Get list of active asynchronous tasks for specified component
*
* @param ui Owning UI
* @return Set of {@link AsyncTask}
*/
@SuppressWarnings("unchecked")
private Set<AsyncTask> getAsyncTasks(UI ui) {
synchronized (ui) {
Set<AsyncTask> asyncTasks = (Set<AsyncTask>) ComponentUtil.getData(ui, getClass().getName());
if (asyncTasks == null) {
asyncTasks = Collections.synchronizedSet(new HashSet<>());
ComponentUtil.setData(ui, getClass().getName(), asyncTasks);
}
return asyncTasks;
}
}
/**
* Add {@link AsyncTask} for current component
*
* @param ui Owning UI
* @param task Task
*/
void addAsyncTask(UI ui, AsyncTask task) {
getAsyncTasks(ui).add(task);
}
/**
* Remove {@link AsyncTask} for current component
*
* @param ui Owning UI
* @param task Task
*/
void removeAsyncTask(UI ui, AsyncTask task) {
getAsyncTasks(ui).remove(task);
}
/**
* Adjust polling interval for specified component.
*
* @param ui UI, associated with current task
*/
void adjustPollingInterval(UI ui) {
Set<AsyncTask> tasks = getAsyncTasks(ui);
synchronized (tasks) {
int newInterval = tasks.stream()
.map(AsyncTask::getPollingInterval)
.sorted()
.findFirst().orElse(Integer.MAX_VALUE);
if (newInterval < Integer.MAX_VALUE) {
if (newInterval != ui.getPollInterval()) {
ui.setPollInterval(newInterval);
}
} else {
if (-1 != ui.getPollInterval()) {
ui.setPollInterval(-1);
}
}
}
}
/**
* Exception handler delegating handling to {@link AsyncManager#exceptionHandler}
*
* @param e Exception to handle
*/
void handleException(Task task, Exception e) {
exceptionHandler.handle(task, e);
}
void handleTaskStateChanged(Task task, TaskStateHandler.State state) {
if (taskStateHandler != null) {
taskStateHandler.taskChanged(task, state);
}
}
/**
* Functional interface for exception handling
*/
@FunctionalInterface
public interface ExceptionHandler {
/**
* Handle exception happened during {@code task} execution.
*
* @param task AsyncTask where exception happened
* @param e Exception
*/
void handle(Task task, Exception e);
}
/**
* Functional interface for task state handling
*/
@FunctionalInterface
public interface TaskStateHandler {
enum State {
/**
* Task started
*/
RUNNING,
/**
* Task done
*/
DONE,
/**
* Task was canceled
*/
CANCELED
}
/**
* Handle state change during task execution.
* @param task Task where state change happened
* @param state State of the given task
*/
void taskChanged(Task task, State state);
}
}