1717package org .springframework .integration .mail ;
1818
1919import java .io .Serial ;
20- import java .time .Instant ;
2120import java .util .List ;
21+ import java .util .concurrent .Executor ;
2222import java .util .concurrent .ScheduledFuture ;
23- import java .util .concurrent .atomic .AtomicBoolean ;
2423import java .util .function .Consumer ;
2524
2625import jakarta .mail .Folder ;
3130import org .springframework .beans .factory .BeanClassLoaderAware ;
3231import org .springframework .context .ApplicationEventPublisher ;
3332import org .springframework .context .ApplicationEventPublisherAware ;
33+ import org .springframework .core .task .SimpleAsyncTaskExecutor ;
3434import org .springframework .integration .endpoint .MessageProducerSupport ;
3535import org .springframework .integration .mail .event .MailIntegrationEvent ;
3636import org .springframework .integration .transaction .IntegrationResourceHolder ;
3737import org .springframework .integration .transaction .IntegrationResourceHolderSynchronization ;
3838import org .springframework .integration .transaction .TransactionSynchronizationFactory ;
3939import org .springframework .messaging .MessagingException ;
40- import org .springframework .scheduling .TaskScheduler ;
41- import org .springframework .scheduling .Trigger ;
42- import org .springframework .scheduling .TriggerContext ;
4340import org .springframework .transaction .support .TransactionSynchronization ;
4441import org .springframework .transaction .support .TransactionSynchronizationManager ;
4542import org .springframework .util .Assert ;
@@ -63,12 +60,10 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport implements Be
6360
6461 private static final int DEFAULT_RECONNECT_DELAY = 10000 ;
6562
66- private final ExceptionAwarePeriodicTrigger receivingTaskTrigger = new ExceptionAwarePeriodicTrigger ();
67-
68- private final IdleTask idleTask = new IdleTask ();
69-
7063 private final ImapMailReceiver mailReceiver ;
7164
65+ private Executor taskExecutor ;
66+
7267 private TransactionSynchronizationFactory transactionSynchronizationFactory ;
7368
7469 private ClassLoader classLoader ;
@@ -100,6 +95,16 @@ public void setAdviceChain(List<Advice> adviceChain) {
10095 this .adviceChain = adviceChain ;
10196 }
10297
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+
103108 /**
104109 * Specify whether the IDLE task should reconnect automatically after
105110 * catching a {@link jakarta.mail.MessagingException} while waiting for messages. The
@@ -139,6 +144,10 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv
139144 protected void onInit () {
140145 super .onInit ();
141146
147+ if (this .taskExecutor == null ) {
148+ this .taskExecutor = new SimpleAsyncTaskExecutor (getBeanName () + "-" );
149+ }
150+
142151 Consumer <?> messageSenderToUse = new MessageSender ();
143152
144153 if (!CollectionUtils .isEmpty (this .adviceChain )) {
@@ -153,16 +162,9 @@ protected void onInit() {
153162 this .messageSender = (Consumer <Object >) messageSenderToUse ;
154163 }
155164
156-
157- /*
158- * Lifecycle implementation
159- */
160-
161- @ Override // guarded by super#lifecycleLock
165+ @ Override
162166 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 );
166168 }
167169
168170 @ Override
@@ -190,6 +192,70 @@ private void publishException(Exception ex) {
190192 }
191193 }
192194
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+
193259 private class MessageSender implements Consumer <Object > {
194260
195261 MessageSender () {
@@ -227,112 +293,6 @@ public void accept(Object mailMessage) {
227293
228294 }
229295
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-
336296 public class ImapIdleExceptionEvent extends MailIntegrationEvent {
337297
338298 @ Serial
0 commit comments