16
16
17
17
package org .springframework .integration .endpoint ;
18
18
19
+ import java .time .Duration ;
19
20
import java .util .Collection ;
21
+ import java .util .Date ;
20
22
import java .util .List ;
21
23
import java .util .concurrent .Callable ;
22
24
import java .util .concurrent .Executor ;
23
25
import java .util .concurrent .ScheduledFuture ;
24
26
import java .util .stream .Collectors ;
25
27
26
28
import org .aopalliance .aop .Advice ;
29
+ import org .reactivestreams .Subscription ;
27
30
28
31
import org .springframework .aop .framework .ProxyFactory ;
29
32
import org .springframework .beans .factory .BeanClassLoaderAware ;
43
46
import org .springframework .messaging .MessagingException ;
44
47
import org .springframework .scheduling .Trigger ;
45
48
import org .springframework .scheduling .support .PeriodicTrigger ;
49
+ import org .springframework .scheduling .support .SimpleTriggerContext ;
46
50
import org .springframework .transaction .interceptor .TransactionInterceptor ;
47
51
import org .springframework .transaction .support .TransactionSynchronization ;
48
52
import org .springframework .transaction .support .TransactionSynchronizationManager ;
51
55
import org .springframework .util .CollectionUtils ;
52
56
import org .springframework .util .ErrorHandler ;
53
57
58
+ import reactor .core .publisher .Flux ;
59
+ import reactor .core .publisher .Mono ;
60
+ import reactor .core .scheduler .Schedulers ;
61
+
54
62
/**
55
63
* @author Mark Fisher
56
64
* @author Oleg Zhurakousky
@@ -66,23 +74,27 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
66
74
67
75
private boolean syncExecutor = true ;
68
76
77
+ private ClassLoader beanClassLoader = ClassUtils .getDefaultClassLoader ();
78
+
79
+ private Trigger trigger = new PeriodicTrigger (10 );
80
+
81
+ private long maxMessagesPerPoll = -1 ;
82
+
69
83
private ErrorHandler errorHandler ;
70
84
71
85
private boolean errorHandlerIsDefault ;
72
86
73
- private Trigger trigger = new PeriodicTrigger (10 );
74
-
75
87
private List <Advice > adviceChain ;
76
88
77
- private ClassLoader beanClassLoader = ClassUtils . getDefaultClassLoader () ;
89
+ private TransactionSynchronizationFactory transactionSynchronizationFactory ;
78
90
79
- private long maxMessagesPerPoll = - 1 ;
91
+ private volatile Callable < Message <?>> pollingTask ;
80
92
81
- private TransactionSynchronizationFactory transactionSynchronizationFactory ;
93
+ private volatile Flux < Message <?>> pollingFlux ;
82
94
83
- private volatile ScheduledFuture <?> runningTask ;
95
+ private volatile Subscription subscription ;
84
96
85
- private volatile Runnable poller ;
97
+ private volatile ScheduledFuture <?> runningTask ;
86
98
87
99
private volatile boolean initialized ;
88
100
@@ -167,6 +179,14 @@ protected boolean isReceiveOnlyAdvice(Advice advice) {
167
179
protected void applyReceiveOnlyAdviceChain (Collection <Advice > chain ) {
168
180
}
169
181
182
+ protected boolean isReactive () {
183
+ return false ;
184
+ }
185
+
186
+ protected Flux <Message <?>> getPollingFlux () {
187
+ return this .pollingFlux ;
188
+ }
189
+
170
190
@ Override
171
191
protected void onInit () {
172
192
synchronized (this .initializationMonitor ) {
@@ -200,16 +220,38 @@ protected void onInit() {
200
220
}
201
221
}
202
222
223
+ // LifecycleSupport implementation
224
+
225
+ @ Override // guarded by super#lifecycleLock
226
+ protected void doStart () {
227
+ if (!this .initialized ) {
228
+ onInit ();
229
+ }
230
+
231
+ this .pollingTask = createPollingTask ();
232
+
233
+ if (isReactive ()) {
234
+ this .pollingFlux = createFluxGenerator ();
235
+ }
236
+ else {
237
+ Assert .state (getTaskScheduler () != null , "unable to start polling, no taskScheduler available" );
238
+
239
+ this .runningTask =
240
+ getTaskScheduler ()
241
+ .schedule (createPoller (), this .trigger );
242
+ }
243
+ }
244
+
203
245
@ SuppressWarnings ("unchecked" )
204
- private Runnable createPoller () throws Exception {
246
+ private Callable < Message <?>> createPollingTask () {
205
247
List <Advice > receiveOnlyAdviceChain = null ;
206
248
if (!CollectionUtils .isEmpty (this .adviceChain )) {
207
249
receiveOnlyAdviceChain = this .adviceChain .stream ()
208
250
.filter (this ::isReceiveOnlyAdvice )
209
251
.collect (Collectors .toList ());
210
252
}
211
253
212
- Callable <Boolean > pollingTask = this ::doPoll ;
254
+ Callable <Message <?> > pollingTask = this ::doPoll ;
213
255
214
256
List <Advice > adviceChain = this .adviceChain ;
215
257
if (!CollectionUtils .isEmpty (adviceChain )) {
@@ -219,65 +261,122 @@ private Runnable createPoller() throws Exception {
219
261
.filter (advice -> !isReceiveOnlyAdvice (advice ))
220
262
.forEach (proxyFactory ::addAdvice );
221
263
}
222
- pollingTask = (Callable <Boolean >) proxyFactory .getProxy (this .beanClassLoader );
264
+ pollingTask = (Callable <Message <?> >) proxyFactory .getProxy (this .beanClassLoader );
223
265
}
224
266
if (!CollectionUtils .isEmpty (receiveOnlyAdviceChain )) {
225
267
applyReceiveOnlyAdviceChain (receiveOnlyAdviceChain );
226
268
}
227
- return new Poller (pollingTask );
269
+
270
+ return pollingTask ;
228
271
}
229
272
230
- // LifecycleSupport implementation
273
+ private Runnable createPoller () {
274
+ return () ->
275
+ this .taskExecutor .execute (() -> {
276
+ int count = 0 ;
277
+ while (this .initialized && (this .maxMessagesPerPoll <= 0 || count < this .maxMessagesPerPoll )) {
278
+ if (pollForMessage () == null ) {
279
+ break ;
280
+ }
281
+ count ++;
282
+ }
283
+ });
284
+ }
231
285
232
- @ Override // guarded by super#lifecycleLock
233
- protected void doStart () {
234
- if (!this .initialized ) {
235
- this .onInit ();
236
- }
237
- Assert .state (this .getTaskScheduler () != null ,
238
- "unable to start polling, no taskScheduler available" );
286
+ private Flux <Message <?>> createFluxGenerator () {
287
+ SimpleTriggerContext triggerContext = new SimpleTriggerContext ();
288
+
289
+ return Flux
290
+ .<Duration >generate (sink -> {
291
+ Date date = this .trigger .nextExecutionTime (triggerContext );
292
+ if (date != null ) {
293
+ triggerContext .update (date , null , null );
294
+ long millis = date .getTime () - System .currentTimeMillis ();
295
+ sink .next (Duration .ofMillis (millis ));
296
+ }
297
+ else {
298
+ sink .complete ();
299
+ }
300
+ })
301
+ .concatMap (duration ->
302
+ Mono .delay (duration )
303
+ .doOnNext (l ->
304
+ triggerContext .update (triggerContext .lastScheduledExecutionTime (),
305
+ new Date (), null ))
306
+ .flatMapMany (l ->
307
+ Flux
308
+ .<Message <?>>generate (fluxSink -> {
309
+ Message <?> message = pollForMessage ();
310
+ if (message != null ) {
311
+ fluxSink .next (message );
312
+ }
313
+ else {
314
+ fluxSink .complete ();
315
+ }
316
+ })
317
+ .take (this .maxMessagesPerPoll )
318
+ .subscribeOn (Schedulers .fromExecutor (this .taskExecutor ))
319
+ .doOnComplete (() ->
320
+ triggerContext .update (triggerContext .lastScheduledExecutionTime (),
321
+ triggerContext .lastActualExecutionTime (),
322
+ new Date ())
323
+ )), 1 )
324
+ .repeat (this ::isRunning )
325
+ .doOnSubscribe (subscription -> this .subscription = subscription );
326
+ }
327
+
328
+ private Message <?> pollForMessage () {
239
329
try {
240
- this .poller = createPoller ();
330
+ return this .pollingTask . call ();
241
331
}
242
332
catch (Exception e ) {
243
- this .initialized = false ;
244
- throw new MessagingException ("Failed to create Poller" , e );
333
+ if (e instanceof MessagingException ) {
334
+ throw (MessagingException ) e ;
335
+ }
336
+ else {
337
+ Message <?> failedMessage = null ;
338
+ if (this .transactionSynchronizationFactory != null ) {
339
+ Object resource = TransactionSynchronizationManager .getResource (getResourceToBind ());
340
+ if (resource instanceof IntegrationResourceHolder ) {
341
+ failedMessage = ((IntegrationResourceHolder ) resource ).getMessage ();
342
+ }
343
+ }
344
+ throw new MessagingException (failedMessage , e );
345
+ }
245
346
}
246
- this . runningTask = this . getTaskScheduler (). schedule ( this . poller , this . trigger );
247
- }
248
-
249
- @ Override // guarded by super#lifecycleLock
250
- protected void doStop () {
251
- if ( this . runningTask != null ) {
252
- this . runningTask . cancel ( true );
347
+ finally {
348
+ if ( this . transactionSynchronizationFactory != null ) {
349
+ Object resource = getResourceToBind ();
350
+ if ( TransactionSynchronizationManager . hasResource ( resource )) {
351
+ TransactionSynchronizationManager . unbindResource ( resource );
352
+ }
353
+ }
253
354
}
254
- this .runningTask = null ;
255
355
}
256
356
257
- private boolean doPoll () {
258
- IntegrationResourceHolder holder = this .bindResourceHolderIfNecessary (
259
- this .getResourceKey (), this .getResourceToBind ());
260
- Message <?> message = null ;
357
+ private Message <?> doPoll () {
358
+ IntegrationResourceHolder holder = bindResourceHolderIfNecessary (getResourceKey (), getResourceToBind ());
359
+ Message <?> message ;
261
360
try {
262
- message = this . receiveMessage ();
361
+ message = receiveMessage ();
263
362
}
264
363
catch (Exception e ) {
265
364
if (Thread .interrupted ()) {
266
365
if (logger .isDebugEnabled ()) {
267
366
logger .debug ("Poll interrupted - during stop()? : " + e .getMessage ());
268
367
}
269
- return false ;
368
+ return null ;
270
369
}
271
370
else {
272
371
throw (RuntimeException ) e ;
273
372
}
274
373
}
275
- boolean result ;
374
+
276
375
if (message == null ) {
277
376
if (this .logger .isDebugEnabled ()) {
278
377
this .logger .debug ("Received no Message during the poll, returning 'false'" );
279
378
}
280
- result = false ;
379
+ return null ;
281
380
}
282
381
else {
283
382
if (this .logger .isDebugEnabled ()) {
@@ -286,20 +385,35 @@ private boolean doPoll() {
286
385
if (holder != null ) {
287
386
holder .setMessage (message );
288
387
}
289
- try {
290
- this .handleMessage (message );
291
- }
292
- catch (Exception e ) {
293
- if (e instanceof MessagingException ) {
294
- throw new MessagingExceptionWrapper (message , (MessagingException ) e );
388
+
389
+ if (!isReactive ()) {
390
+ try {
391
+ handleMessage (message );
295
392
}
296
- else {
297
- throw new MessagingException (message , e );
393
+ catch (Exception e ) {
394
+ if (e instanceof MessagingException ) {
395
+ throw new MessagingExceptionWrapper (message , (MessagingException ) e );
396
+ }
397
+ else {
398
+ throw new MessagingException (message , e );
399
+ }
298
400
}
299
401
}
300
- result = true ;
301
402
}
302
- return result ;
403
+
404
+ return message ;
405
+ }
406
+
407
+ @ Override // guarded by super#lifecycleLock
408
+ protected void doStop () {
409
+ if (this .runningTask != null ) {
410
+ this .runningTask .cancel (true );
411
+ }
412
+ this .runningTask = null ;
413
+
414
+ if (this .subscription != null ) {
415
+ this .subscription .cancel ();
416
+ }
303
417
}
304
418
305
419
/**
@@ -369,57 +483,4 @@ private IntegrationResourceHolder bindResourceHolderIfNecessary(String key, Obje
369
483
return null ;
370
484
}
371
485
372
- /**
373
- * Default Poller implementation
374
- */
375
- private final class Poller implements Runnable {
376
-
377
- private final Callable <Boolean > pollingTask ;
378
-
379
- Poller (Callable <Boolean > pollingTask ) {
380
- this .pollingTask = pollingTask ;
381
- }
382
-
383
- @ Override
384
- public void run () {
385
- AbstractPollingEndpoint .this .taskExecutor .execute (() -> {
386
- int count = 0 ;
387
- while (AbstractPollingEndpoint .this .initialized
388
- && (AbstractPollingEndpoint .this .maxMessagesPerPoll <= 0
389
- || count < AbstractPollingEndpoint .this .maxMessagesPerPoll )) {
390
- try {
391
- if (!Poller .this .pollingTask .call ()) {
392
- break ;
393
- }
394
- count ++;
395
- }
396
- catch (Exception e ) {
397
- if (e instanceof MessagingException ) {
398
- throw (MessagingException ) e ;
399
- }
400
- else {
401
- Message <?> failedMessage = null ;
402
- if (AbstractPollingEndpoint .this .transactionSynchronizationFactory != null ) {
403
- Object resource = TransactionSynchronizationManager .getResource (getResourceToBind ());
404
- if (resource instanceof IntegrationResourceHolder ) {
405
- failedMessage = ((IntegrationResourceHolder ) resource ).getMessage ();
406
- }
407
- }
408
- throw new MessagingException (failedMessage , e );
409
- }
410
- }
411
- finally {
412
- if (AbstractPollingEndpoint .this .transactionSynchronizationFactory != null ) {
413
- Object resource = getResourceToBind ();
414
- if (TransactionSynchronizationManager .hasResource (resource )) {
415
- TransactionSynchronizationManager .unbindResource (resource );
416
- }
417
- }
418
- }
419
- }
420
- });
421
- }
422
-
423
- }
424
-
425
486
}
0 commit comments