Skip to content

Commit 5f70d30

Browse files
committed
spring-projectsGH-3017: Expose cancel-idle-interval XML attribute
Fixes spring-projects#3017 * Expose also a `ImapIdleChannelAdapterSpec.cancelIdleInterval()` option * Deprecate useless `ImapMailInboundChannelAdapterSpec.cancelIdleInterval()`
1 parent fab4c45 commit 5f70d30

File tree

7 files changed

+62
-29
lines changed

7 files changed

+62
-29
lines changed

spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private BeanDefinition parseImapMailReceiver(Element element, ParserContext pars
102102
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes");
103103
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content");
104104
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder");
105-
105+
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "cancel-idle-interval");
106106
return receiverBuilder.getBeanDefinition();
107107
}
108108
}

spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapIdleChannelAdapterSpec.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,20 @@ public ImapIdleChannelAdapterSpec shouldReconnectAutomatically(boolean shouldRec
368368
return this;
369369
}
370370

371+
/**
372+
* How often to recycle the idle task (in case of a silently dropped connection).
373+
* Seconds; default 120 (2 minutes).
374+
* @param interval the interval.
375+
* @return the spec.
376+
* @see ImapMailReceiver#setCancelIdleInterval(long)
377+
* @since 5.2
378+
*/
379+
public ImapIdleChannelAdapterSpec cancelIdleInterval(long interval) {
380+
assertReceiver();
381+
this.receiver.setCancelIdleInterval(interval);
382+
return this;
383+
}
384+
371385
@Override
372386
public Map<Object, String> getComponentsToRegister() {
373387
return this.componentsToRegister;

spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapMailInboundChannelAdapterSpec.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*
2525
* @author Gary Russell
2626
* @author Artem Bilan
27+
*
2728
* @since 5.0
2829
*/
2930
public class ImapMailInboundChannelAdapterSpec
@@ -72,12 +73,13 @@ public ImapMailInboundChannelAdapterSpec shouldMarkMessagesAsRead(boolean should
7273
* @return the spec.
7374
* @see ImapMailReceiver#setCancelIdleInterval(long)
7475
* @since 5.0.10
76+
* @deprecated since 5.2: there is no idle task started for polling channel adapter.
7577
*/
78+
@Deprecated
7679
public ImapMailInboundChannelAdapterSpec cancelIdleInterval(long interval) {
7780
assertReceiver();
7881
this.receiver.setCancelIdleInterval(interval);
7982
return this;
8083
}
8184

82-
8385
}

spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-5.2.xsd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,14 @@
171171
</xsd:appinfo>
172172
</xsd:annotation>
173173
</xsd:attribute>
174+
<xsd:attribute name="cancel-idle-interval" type="xsd:string">
175+
<xsd:annotation>
176+
<xsd:documentation>
177+
How often to recycle the idle task (in case of a silently dropped connection).
178+
Seconds; default 120 (2 minutes).
179+
</xsd:documentation>
180+
</xsd:annotation>
181+
</xsd:attribute>
174182
</xsd:extension>
175183
</xsd:complexContent>
176184
</xsd:complexType>

spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
simple-content="true"
4040
embedded-parts-as-bytes="false"
4141
auto-close-folder="false"
42-
should-delete-messages="true"/>
42+
should-delete-messages="true"
43+
cancel-idle-interval="202"/>
4344

4445
<bean id="mapper" class="org.springframework.integration.mail.support.DefaultMailHeaderMapper" />
4546

spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests.java

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,26 @@ public class ImapIdleChannelAdapterParserTests {
5858
@Autowired
5959
private MessageChannel autoChannel;
6060

61-
@Autowired @Qualifier("autoChannel.adapter")
61+
@Autowired
62+
@Qualifier("autoChannel.adapter")
6263
private ImapIdleChannelAdapter autoChannelAdapter;
6364

6465
@Test
6566
public void simpleAdapter() {
6667
Object adapter = context.getBean("simpleAdapter");
67-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
68+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
6869
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
6970
Object channel = context.getBean("channel");
7071
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
7172
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
7273
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
73-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
74+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
7475
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
7576
Object url = receiverAccessor.getPropertyValue("url");
7677
assertThat(url).isEqualTo(new URLName("imap:foo"));
7778
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
7879
// mail.imap(s).peek properties
79-
assertThat(properties.size()).isEqualTo(2);
80+
assertThat(properties).hasSize(2);
8081
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
8182
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
8283
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isNull();
@@ -85,24 +86,25 @@ public void simpleAdapter() {
8586
assertThat(receiverAccessor.getPropertyValue("headerMapper")).isNotNull();
8687
assertThat(receiverAccessor.getPropertyValue("simpleContent")).isEqualTo(Boolean.TRUE);
8788
assertThat(receiverAccessor.getPropertyValue("autoCloseFolder")).isEqualTo(false);
89+
assertThat(receiverAccessor.getPropertyValue("cancelIdleInterval")).isEqualTo(202000L);
8890
}
8991

9092
@Test
9193
public void simpleAdapterWithErrorChannel() {
9294
Object adapter = context.getBean("simpleAdapterWithErrorChannel");
93-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
95+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
9496
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
9597
Object channel = context.getBean("channel");
9698
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
9799
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
98100
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
99-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
101+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
100102
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
101103
Object url = receiverAccessor.getPropertyValue("url");
102104
assertThat(url).isEqualTo(new URLName("imap:foo"));
103105
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
104106
// mail.imap(s).peek properties
105-
assertThat(properties.size()).isEqualTo(2);
107+
assertThat(properties).hasSize(2);
106108
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
107109
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
108110
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isSameAs(context.getBean("errorChannel"));
@@ -112,56 +114,56 @@ public void simpleAdapterWithErrorChannel() {
112114
}
113115

114116
@Test
115-
public void simpleAdapterWithMarkeMessagesAsRead() {
116-
Object adapter = context.getBean("simpleAdapterMarkAsRead");
117-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
117+
public void simpleAdapterWithMarkMessagesAsRead() {
118+
Object adapter = this.context.getBean("simpleAdapterMarkAsRead");
119+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
118120
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
119-
Object channel = context.getBean("channel");
121+
Object channel = this.context.getBean("channel");
120122
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
121123
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
122124
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
123-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
125+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
124126
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
125127
Object url = receiverAccessor.getPropertyValue("url");
126128
assertThat(url).isEqualTo(new URLName("imap:foo"));
127129
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
128130
// mail.imap(s).peek properties
129-
assertThat(properties.size()).isEqualTo(2);
131+
assertThat(properties).hasSize(2);
130132
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
131133
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
132134
assertThat(receiverAccessor.getPropertyValue("userFlag")).isEqualTo("flagged");
133135
}
134136

135137
@Test
136-
public void simpleAdapterWithMarkeMessagesAsReadFalse() {
138+
public void simpleAdapterWithMarkMessagesAsReadFalse() {
137139
Object adapter = context.getBean("simpleAdapterMarkAsReadFalse");
138-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
140+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
139141
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
140142
Object channel = context.getBean("channel");
141143
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
142144
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
143145
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
144-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
146+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
145147
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
146148
Object url = receiverAccessor.getPropertyValue("url");
147149
assertThat(url).isEqualTo(new URLName("imap:foo"));
148150
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
149151
// mail.imap(s).peek properties
150-
assertThat(properties.size()).isEqualTo(2);
152+
assertThat(properties).hasSize(2);
151153
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
152154
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.FALSE);
153155
}
154156

155157
@Test
156158
public void customAdapter() {
157159
Object adapter = context.getBean("customAdapter");
158-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
160+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
159161
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
160162
Object channel = context.getBean("channel");
161163
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
162164
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
163165
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
164-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
166+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
165167
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
166168
Object url = receiverAccessor.getPropertyValue("url");
167169
assertThat(url).isEqualTo(new URLName("imap:foo"));
@@ -180,25 +182,26 @@ public void testAutoChannel() {
180182
@Test
181183
public void transactionalAdapter() {
182184
Object adapter = context.getBean("transactionalAdapter");
183-
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
185+
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
184186
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
185187
Object channel = context.getBean("channel");
186188
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
187189
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
188190
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
189-
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
191+
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
190192
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
191193
Object url = receiverAccessor.getPropertyValue("url");
192194
assertThat(url).isEqualTo(new URLName("imap:foo"));
193195
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
194196
// mail.imap(s).peek properties
195-
assertThat(properties.size()).isEqualTo(2);
197+
assertThat(properties).hasSize(2);
196198
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
197199
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
198200
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isNull();
199201
assertThat(adapterAccessor.getPropertyValue("sendingTaskExecutor")).isEqualTo(context.getBean("executor"));
200202
assertThat(adapterAccessor.getPropertyValue("adviceChain")).isNotNull();
201203
}
204+
202205
public static class TestSearchTermStrategy implements SearchTermStrategy {
203206

204207
@Override
@@ -207,4 +210,5 @@ public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
207210
}
208211

209212
}
213+
210214
}

src/reference/asciidoc/mail.adoc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ By default, the `ImapMailReceiver` searches for messages based on the default `S
353353

354354
The custom user flag is `spring-integration-mail-adapter`, but you can configure it.
355355
Since version 2.2, the `SearchTerm` used by the `ImapMailReceiver` is fully configurable with `SearchTermStrategy`, which you can inject by using the `search-term-strategy` attribute.
356-
`SearchTermStrategy` is a strategy interface with a single method that lets you create an instance of the `SearchTerm` used by the `ImapMailReceiver`.
356+
A `SearchTermStrategy` is a strategy interface with a single method that lets you create an instance of the `SearchTerm` used by the `ImapMailReceiver`.
357357
The following listing shows the `SearchTermStrategy` interface:
358358

359359
====
@@ -417,8 +417,7 @@ IMPORTANT: In both configurations, `channel` and `should-delete-messages` are re
417417
You should understand why `should-delete-messages` is required.
418418
The issue is with the POP3 protocol, which does not have any knowledge of messages that were read.
419419
It can only know what has been read within a single session.
420-
This means that, when your POP3 mail adapter runs, emails are successfully consumed as as they become available during each poll
421-
and no single email message is delivered more then once.
420+
This means that, when your POP3 mail adapter runs, emails are successfully consumed as as they become available during each poll and no single email message is delivered more then once.
422421
However, as soon as you restart your adapter and begin a new session, all the email messages that might have been retrieved in the previous session are retrieved again.
423422
That is the nature of POP3.
424423
Some might argue that `should-delete-messages` should be `true` by default.
@@ -434,6 +433,10 @@ Leaving it up to you also means that you are less likely to end up with unintend
434433
NOTE: When configuring a polling email adapter's `should-mark-messages-as-read` attribute, you should be aware of the protocol you are configuring to retrieve messages.
435434
For example, POP3 does not support this flag, which means setting it to either value has no effect, as messages are not marked as read.
436435

436+
In case of silently dropped connection, an idle cancel task is run on background periodically (a new IDLE will usually immediately be processed).
437+
To control this interval, a `cancelIdleInterval` option is provided; default 120 (2 minutes).
438+
RFC 2177 recommends an interval no larger than 29 minutes.
439+
437440
[IMPORTANT]
438441
=====
439442
You should understand that that these actions (marking messages read and deleting messages) are performed after the messages are received but before they are processed.
@@ -502,7 +505,8 @@ In the preceding example, thanks to the `mail-filter-expression` attribute, only
502505

503506
Another reasonable question is what happens on the next poll or idle event or what happens when such an adapter is restarted.
504507
Can there be duplication of massages to be filtered? In other words, if, on the last retrieval where you had five new messages and only one passed the filter, what would happen with the other four?
505-
Would they go through the filtering logic again on the next poll or idle? After all, they were not marked as `SEEN`.
508+
Would they go through the filtering logic again on the next poll or idle?
509+
After all, they were not marked as `SEEN`.
506510
The answer is no.
507511
They would not be subject to duplicate processing due to another flag (`RECENT`) that is set by the email server and is used by the Spring Integration mail search filter.
508512
Folder implementations set this flag to indicate that this message is new to this folder.

0 commit comments

Comments
 (0)