Skip to content

Commit 891caed

Browse files
filiphrartembilan
authored andcommitted
GH-9546: IMAP: Flag messages after messages have been copied
Fixes: #9546 Issue link: #9546 Sometimes the IMAP connection breaks just after the message flags have been set, but the message has not been copied yet. This then leads to the message never being received by (as it has been flagged and the next search will not return it). * Flag and maybe delete messages after messages have been copied # Conflicts: # spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java # Conflicts: # spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java
1 parent 6ea0499 commit 891caed

File tree

2 files changed

+70
-8
lines changed

2 files changed

+70
-8
lines changed

Diff for: spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -67,6 +67,7 @@
6767
* @author Artem Bilan
6868
* @author Dominik Simmen
6969
* @author Yuxin Wang
70+
* @author Filip Hrisafov
7071
*/
7172
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean {
7273

@@ -502,18 +503,27 @@ private Object byteArrayToContent(Map<String, Object> headers, ByteArrayOutputSt
502503
}
503504

504505
private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException {
505-
setMessageFlags(filteredMessages);
506-
507-
if (shouldDeleteMessages()) {
508-
deleteMessages(filteredMessages);
509-
}
510506
// Copy messages to cause an eager fetch
507+
Message[] messages = filteredMessages;
511508
if (this.headerMapper == null && (this.autoCloseFolder || this.simpleContent)) {
509+
messages = new Message[filteredMessages.length];
512510
for (int i = 0; i < filteredMessages.length; i++) {
513-
MimeMessage mimeMessage = new IntegrationMimeMessage((MimeMessage) filteredMessages[i]);
511+
Message originalMessage = filteredMessages[i];
512+
messages[i] = originalMessage;
513+
MimeMessage mimeMessage = new IntegrationMimeMessage((MimeMessage) originalMessage);
514514
filteredMessages[i] = mimeMessage;
515515
}
516516
}
517+
518+
setMessageFlagsAndMaybeDeleteMessages(messages);
519+
}
520+
521+
private void setMessageFlagsAndMaybeDeleteMessages(Message[] messages) throws MessagingException {
522+
setMessageFlags(messages);
523+
524+
if (shouldDeleteMessages()) {
525+
deleteMessages(messages);
526+
}
517527
}
518528

519529
private void setMessageFlags(Message[] filteredMessages) throws MessagingException {

Diff for: spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java

+53-1
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package org.springframework.integration.mail;
1818

1919
import java.io.IOException;
20+
import java.io.OutputStream;
2021
import java.lang.reflect.Field;
2122
import java.util.ArrayList;
2223
import java.util.Arrays;
2324
import java.util.List;
2425
import java.util.Properties;
2526
import java.util.concurrent.CountDownLatch;
2627
import java.util.concurrent.TimeUnit;
28+
import java.util.concurrent.atomic.AtomicBoolean;
2729
import java.util.concurrent.atomic.AtomicInteger;
2830
import java.util.concurrent.atomic.AtomicReference;
2931
import java.util.logging.Handler;
@@ -88,6 +90,7 @@
8890
import org.springframework.util.MimeTypeUtils;
8991

9092
import static org.assertj.core.api.Assertions.assertThat;
93+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9194
import static org.mockito.ArgumentMatchers.any;
9295
import static org.mockito.ArgumentMatchers.anyString;
9396
import static org.mockito.BDDMockito.given;
@@ -107,6 +110,7 @@
107110
* @author Artem Bilan
108111
* @author Alexander Pinske
109112
* @author Dominik Simmen
113+
* @author Filip Hrisafov
110114
*/
111115
@SpringJUnitConfig
112116
@ContextConfiguration(
@@ -299,6 +303,11 @@ public void receiveAndMarkAsReadDontDelete() throws Exception {
299303

300304
private AbstractMailReceiver receiveAndMarkAsReadDontDeleteGuts(AbstractMailReceiver receiver, Message msg1,
301305
Message msg2) throws NoSuchFieldException, IllegalAccessException, MessagingException {
306+
return receiveAndMarkAsReadDontDeleteGuts(receiver, msg1, msg2, true);
307+
}
308+
309+
private AbstractMailReceiver receiveAndMarkAsReadDontDeleteGuts(AbstractMailReceiver receiver, Message msg1,
310+
Message msg2, boolean receive) throws NoSuchFieldException, IllegalAccessException, MessagingException {
302311

303312
((ImapMailReceiver) receiver).setShouldMarkMessagesAsRead(true);
304313
receiver = spy(receiver);
@@ -326,7 +335,9 @@ private AbstractMailReceiver receiveAndMarkAsReadDontDeleteGuts(AbstractMailRece
326335
willAnswer(invocation -> messages).given(folder).search(any(SearchTerm.class));
327336

328337
willAnswer(invocation -> null).given(receiver).fetchMessages(messages);
329-
receiver.receive();
338+
if (receive) {
339+
receiver.receive();
340+
}
330341
return receiver;
331342
}
332343

@@ -983,6 +994,30 @@ private void setUpScheduler(ImapMailReceiver mailReceiver, ThreadPoolTaskSchedul
983994
mailReceiver.setBeanFactory(bf);
984995
}
985996

997+
@Test
998+
public void receiveAndMarkAsReadDontDeleteWithThrowingWhenCopying() throws Exception {
999+
AbstractMailReceiver receiver = new ImapMailReceiver();
1000+
MimeMessage msg1 = spy(GreenMailUtil.newMimeMessage("test1"));
1001+
MimeMessage greenMailMsg2 = GreenMailUtil.newMimeMessage("test2");
1002+
TestThrowingMimeMessage msg2 = new TestThrowingMimeMessage(greenMailMsg2);
1003+
receiver = receiveAndMarkAsReadDontDeleteGuts(receiver, msg1, msg2, false);
1004+
assertThatThrownBy(receiver::receive)
1005+
.isInstanceOf(MessagingException.class)
1006+
.hasMessage("IOException while copying message")
1007+
.cause()
1008+
.isInstanceOf(IOException.class)
1009+
.hasMessage("Simulated exception");
1010+
assertThat(msg1.getFlags().contains(Flag.SEEN)).isFalse();
1011+
assertThat(msg2.getFlags().contains(Flag.SEEN)).isFalse();
1012+
verify(msg1, times(0)).setFlags(Mockito.any(), Mockito.anyBoolean());
1013+
1014+
receiver.receive();
1015+
assertThat(msg1.getFlags().contains(Flag.SEEN)).isTrue();
1016+
assertThat(msg2.getFlags().contains(Flag.SEEN)).isTrue();
1017+
// msg2 is marked with the user and seen flags
1018+
verify(msg1, times(2)).setFlags(Mockito.any(), Mockito.anyBoolean());
1019+
verify(receiver, times(0)).deleteMessages(Mockito.any());
1020+
}
9861021

9871022
private static class ImapSearchLoggingHandler extends Handler {
9881023

@@ -1019,4 +1054,21 @@ public void close() throws SecurityException {
10191054

10201055
}
10211056

1057+
private static class TestThrowingMimeMessage extends MimeMessage {
1058+
1059+
protected final AtomicBoolean throwExceptionBeforeWrite = new AtomicBoolean(true);
1060+
1061+
private TestThrowingMimeMessage(MimeMessage source) throws MessagingException {
1062+
super(source);
1063+
}
1064+
1065+
@Override
1066+
public void writeTo(OutputStream os) throws IOException, MessagingException {
1067+
if (this.throwExceptionBeforeWrite.getAndSet(false)) {
1068+
throw new IOException("Simulated exception");
1069+
}
1070+
super.writeTo(os);
1071+
}
1072+
}
1073+
10221074
}

0 commit comments

Comments
 (0)