17
17
package org .springframework .integration .mail ;
18
18
19
19
import java .io .Serial ;
20
- import java .time .Instant ;
21
20
import java .util .List ;
21
+ import java .util .concurrent .Executor ;
22
22
import java .util .concurrent .ScheduledFuture ;
23
- import java .util .concurrent .atomic .AtomicBoolean ;
24
23
import java .util .function .Consumer ;
25
24
26
25
import jakarta .mail .Folder ;
31
30
import org .springframework .beans .factory .BeanClassLoaderAware ;
32
31
import org .springframework .context .ApplicationEventPublisher ;
33
32
import org .springframework .context .ApplicationEventPublisherAware ;
33
+ import org .springframework .core .task .SimpleAsyncTaskExecutor ;
34
34
import org .springframework .integration .endpoint .MessageProducerSupport ;
35
35
import org .springframework .integration .mail .event .MailIntegrationEvent ;
36
36
import org .springframework .integration .transaction .IntegrationResourceHolder ;
37
37
import org .springframework .integration .transaction .IntegrationResourceHolderSynchronization ;
38
38
import org .springframework .integration .transaction .TransactionSynchronizationFactory ;
39
39
import org .springframework .messaging .MessagingException ;
40
- import org .springframework .scheduling .TaskScheduler ;
41
- import org .springframework .scheduling .Trigger ;
42
- import org .springframework .scheduling .TriggerContext ;
43
40
import org .springframework .transaction .support .TransactionSynchronization ;
44
41
import org .springframework .transaction .support .TransactionSynchronizationManager ;
45
42
import org .springframework .util .Assert ;
@@ -63,12 +60,10 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport implements Be
63
60
64
61
private static final int DEFAULT_RECONNECT_DELAY = 10000 ;
65
62
66
- private final ExceptionAwarePeriodicTrigger receivingTaskTrigger = new ExceptionAwarePeriodicTrigger ();
67
-
68
- private final IdleTask idleTask = new IdleTask ();
69
-
70
63
private final ImapMailReceiver mailReceiver ;
71
64
65
+ private Executor taskExecutor ;
66
+
72
67
private TransactionSynchronizationFactory transactionSynchronizationFactory ;
73
68
74
69
private ClassLoader classLoader ;
@@ -100,6 +95,16 @@ public void setAdviceChain(List<Advice> adviceChain) {
100
95
this .adviceChain = adviceChain ;
101
96
}
102
97
98
+ /**
99
+ * Provide a managed {@link Executor} to schedule a receiving IDLE task.
100
+ * @param taskExecutor the {@link Executor} to use.
101
+ * @since 6.2
102
+ */
103
+ public void setTaskExecutor (Executor taskExecutor ) {
104
+ Assert .notNull (taskExecutor , "'taskExecutor' must not be null" );
105
+ this .taskExecutor = taskExecutor ;
106
+ }
107
+
103
108
/**
104
109
* Specify whether the IDLE task should reconnect automatically after
105
110
* catching a {@link jakarta.mail.MessagingException} while waiting for messages. The
@@ -139,6 +144,10 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv
139
144
protected void onInit () {
140
145
super .onInit ();
141
146
147
+ if (this .taskExecutor == null ) {
148
+ this .taskExecutor = new SimpleAsyncTaskExecutor (getBeanName () + "-" );
149
+ }
150
+
142
151
Consumer <?> messageSenderToUse = new MessageSender ();
143
152
144
153
if (!CollectionUtils .isEmpty (this .adviceChain )) {
@@ -153,16 +162,9 @@ protected void onInit() {
153
162
this .messageSender = (Consumer <Object >) messageSenderToUse ;
154
163
}
155
164
156
-
157
- /*
158
- * Lifecycle implementation
159
- */
160
-
161
- @ Override // guarded by super#lifecycleLock
165
+ @ Override
162
166
protected void doStart () {
163
- TaskScheduler scheduler = getTaskScheduler ();
164
- Assert .notNull (scheduler , "'taskScheduler' must not be null" );
165
- this .receivingTask = scheduler .schedule (new ReceivingTask (), this .receivingTaskTrigger );
167
+ this .taskExecutor .execute (this ::callIdle );
166
168
}
167
169
168
170
@ Override
@@ -190,6 +192,70 @@ private void publishException(Exception ex) {
190
192
}
191
193
}
192
194
195
+ private void callIdle () {
196
+ while (isActive ()) {
197
+ try {
198
+ processIdle ();
199
+ logger .debug ("Task completed successfully. Re-scheduling it again right away." );
200
+ }
201
+ catch (Exception ex ) {
202
+ publishException (ex );
203
+ if (this .shouldReconnectAutomatically
204
+ && ex .getCause () instanceof jakarta .mail .MessagingException messagingException ) {
205
+
206
+ //run again after a delay
207
+ logger .info (messagingException ,
208
+ () -> "Failed to execute IDLE task. Will attempt to resubmit in "
209
+ + this .reconnectDelay + " milliseconds." );
210
+ delayNextIdleCall ();
211
+ }
212
+ else {
213
+ logger .warn (ex ,
214
+ "Failed to execute IDLE task. " +
215
+ "Won't resubmit since not a 'shouldReconnectAutomatically' " +
216
+ "or not a 'jakarta.mail.MessagingException'" );
217
+ break ;
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ private void processIdle () {
224
+ try {
225
+ logger .debug ("waiting for mail" );
226
+ this .mailReceiver .waitForNewMessages ();
227
+ Folder folder = this .mailReceiver .getFolder ();
228
+ if (folder != null && folder .isOpen () && isRunning ()) {
229
+ Object [] mailMessages = this .mailReceiver .receive ();
230
+ logger .debug (() -> "received " + mailMessages .length + " mail messages" );
231
+ for (Object mailMessage : mailMessages ) {
232
+ if (isRunning ()) {
233
+ this .messageSender .accept (mailMessage );
234
+ }
235
+ }
236
+ }
237
+ }
238
+ catch (jakarta .mail .MessagingException ex ) {
239
+ logger .warn (ex , "error occurred in idle task" );
240
+ if (this .shouldReconnectAutomatically ) {
241
+ throw new IllegalStateException ("Failure in 'idle' task. Will resubmit." , ex );
242
+ }
243
+ else {
244
+ throw new MessagingException ("Failure in 'idle' task. Will NOT resubmit." , ex );
245
+ }
246
+ }
247
+ }
248
+
249
+ private void delayNextIdleCall () {
250
+ try {
251
+ Thread .sleep (this .reconnectDelay );
252
+ }
253
+ catch (InterruptedException ex ) {
254
+ Thread .currentThread ().interrupt ();
255
+ throw new IllegalStateException (ex );
256
+ }
257
+ }
258
+
193
259
private class MessageSender implements Consumer <Object > {
194
260
195
261
MessageSender () {
@@ -227,112 +293,6 @@ public void accept(Object mailMessage) {
227
293
228
294
}
229
295
230
- private class ReceivingTask implements Runnable {
231
-
232
- ReceivingTask () {
233
- }
234
-
235
- @ Override
236
- public void run () {
237
- if (isRunning ()) {
238
- try {
239
- ImapIdleChannelAdapter .this .idleTask .run ();
240
- logger .debug ("Task completed successfully. Re-scheduling it again right away." );
241
- }
242
- catch (Exception ex ) {
243
- if (ImapIdleChannelAdapter .this .shouldReconnectAutomatically
244
- && ex .getCause () instanceof jakarta .mail .MessagingException messagingException ) {
245
-
246
- //run again after a delay
247
- logger .info (messagingException ,
248
- () -> "Failed to execute IDLE task. Will attempt to resubmit in "
249
- + ImapIdleChannelAdapter .this .reconnectDelay + " milliseconds." );
250
- ImapIdleChannelAdapter .this .receivingTaskTrigger .delayNextExecution ();
251
- }
252
- else {
253
- logger .warn (ex ,
254
- "Failed to execute IDLE task. " +
255
- "Won't resubmit since not a 'shouldReconnectAutomatically' " +
256
- "or not a 'jakarta.mail.MessagingException'" );
257
- ImapIdleChannelAdapter .this .receivingTaskTrigger .stop ();
258
- }
259
- publishException (ex );
260
- }
261
- }
262
- }
263
-
264
- }
265
-
266
-
267
- private class IdleTask implements Runnable {
268
-
269
- IdleTask () {
270
- }
271
-
272
- @ Override
273
- public void run () {
274
- if (isRunning ()) {
275
- try {
276
- logger .debug ("waiting for mail" );
277
- ImapIdleChannelAdapter .this .mailReceiver .waitForNewMessages ();
278
- Folder folder = ImapIdleChannelAdapter .this .mailReceiver .getFolder ();
279
- if (folder != null && folder .isOpen () && isRunning ()) {
280
- Object [] mailMessages = ImapIdleChannelAdapter .this .mailReceiver .receive ();
281
- logger .debug (() -> "received " + mailMessages .length + " mail messages" );
282
- for (Object mailMessage : mailMessages ) {
283
- if (isRunning ()) {
284
- ImapIdleChannelAdapter .this .messageSender .accept (mailMessage );
285
- }
286
- }
287
- }
288
- }
289
- catch (jakarta .mail .MessagingException ex ) {
290
- logger .warn (ex , "error occurred in idle task" );
291
- if (ImapIdleChannelAdapter .this .shouldReconnectAutomatically ) {
292
- throw new IllegalStateException ("Failure in 'idle' task. Will resubmit." , ex );
293
- }
294
- else {
295
- throw new MessagingException ("Failure in 'idle' task. Will NOT resubmit." , ex );
296
- }
297
- }
298
- }
299
- }
300
-
301
- }
302
-
303
- private class ExceptionAwarePeriodicTrigger implements Trigger {
304
-
305
- private final AtomicBoolean delayNextExecution = new AtomicBoolean ();
306
-
307
- private final AtomicBoolean stop = new AtomicBoolean ();
308
-
309
-
310
- ExceptionAwarePeriodicTrigger () {
311
- }
312
-
313
- @ Override
314
- public Instant nextExecution (TriggerContext triggerContext ) {
315
- if (this .stop .getAndSet (false )) {
316
- return null ;
317
- }
318
- if (this .delayNextExecution .getAndSet (false )) {
319
- return Instant .now ().plusMillis (ImapIdleChannelAdapter .this .reconnectDelay );
320
- }
321
- else {
322
- return Instant .now ();
323
- }
324
- }
325
-
326
- void delayNextExecution () {
327
- this .delayNextExecution .set (true );
328
- }
329
-
330
- void stop () {
331
- this .stop .set (true );
332
- }
333
-
334
- }
335
-
336
296
public class ImapIdleExceptionEvent extends MailIntegrationEvent {
337
297
338
298
@ Serial
0 commit comments