Skip to content

GH-3017: Expose cancel-idle-interval XML attribute #3034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private BeanDefinition parseImapMailReceiver(Element element, ParserContext pars
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes");
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content");
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder");

IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "cancel-idle-interval");
return receiverBuilder.getBeanDefinition();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,20 @@ public ImapIdleChannelAdapterSpec shouldReconnectAutomatically(boolean shouldRec
return this;
}

/**
* How often to recycle the idle task (in case of a silently dropped connection).
* Seconds; default 120 (2 minutes).
* @param interval the interval.
* @return the spec.
* @see ImapMailReceiver#setCancelIdleInterval(long)
* @since 5.2
*/
public ImapIdleChannelAdapterSpec cancelIdleInterval(long interval) {
assertReceiver();
this.receiver.setCancelIdleInterval(interval);
return this;
}

@Override
public Map<Object, String> getComponentsToRegister() {
return this.componentsToRegister;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*
* @author Gary Russell
* @author Artem Bilan
*
* @since 5.0
*/
public class ImapMailInboundChannelAdapterSpec
Expand Down Expand Up @@ -72,12 +73,13 @@ public ImapMailInboundChannelAdapterSpec shouldMarkMessagesAsRead(boolean should
* @return the spec.
* @see ImapMailReceiver#setCancelIdleInterval(long)
* @since 5.0.10
* @deprecated since 5.2: there is no idle task started for polling channel adapter.
*/
@Deprecated
public ImapMailInboundChannelAdapterSpec cancelIdleInterval(long interval) {
assertReceiver();
this.receiver.setCancelIdleInterval(interval);
return this;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cancel-idle-interval" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
How often to recycle the idle task (in case of a silently dropped connection).
Seconds; default 120 (2 minutes).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
simple-content="true"
embedded-parts-as-bytes="false"
auto-close-folder="false"
should-delete-messages="true"/>
should-delete-messages="true"
cancel-idle-interval="202"/>

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,26 @@ public class ImapIdleChannelAdapterParserTests {
@Autowired
private MessageChannel autoChannel;

@Autowired @Qualifier("autoChannel.adapter")
@Autowired
@Qualifier("autoChannel.adapter")
private ImapIdleChannelAdapter autoChannelAdapter;

@Test
public void simpleAdapter() {
Object adapter = context.getBean("simpleAdapter");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
// mail.imap(s).peek properties
assertThat(properties.size()).isEqualTo(2);
assertThat(properties).hasSize(2);
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isNull();
Expand All @@ -85,24 +86,25 @@ public void simpleAdapter() {
assertThat(receiverAccessor.getPropertyValue("headerMapper")).isNotNull();
assertThat(receiverAccessor.getPropertyValue("simpleContent")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("autoCloseFolder")).isEqualTo(false);
assertThat(receiverAccessor.getPropertyValue("cancelIdleInterval")).isEqualTo(202000L);
}

@Test
public void simpleAdapterWithErrorChannel() {
Object adapter = context.getBean("simpleAdapterWithErrorChannel");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
// mail.imap(s).peek properties
assertThat(properties.size()).isEqualTo(2);
assertThat(properties).hasSize(2);
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isSameAs(context.getBean("errorChannel"));
Expand All @@ -112,56 +114,56 @@ public void simpleAdapterWithErrorChannel() {
}

@Test
public void simpleAdapterWithMarkeMessagesAsRead() {
Object adapter = context.getBean("simpleAdapterMarkAsRead");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
public void simpleAdapterWithMarkMessagesAsRead() {
Object adapter = this.context.getBean("simpleAdapterMarkAsRead");
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
Object channel = this.context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
// mail.imap(s).peek properties
assertThat(properties.size()).isEqualTo(2);
assertThat(properties).hasSize(2);
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("userFlag")).isEqualTo("flagged");
}

@Test
public void simpleAdapterWithMarkeMessagesAsReadFalse() {
public void simpleAdapterWithMarkMessagesAsReadFalse() {
Object adapter = context.getBean("simpleAdapterMarkAsReadFalse");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
// mail.imap(s).peek properties
assertThat(properties.size()).isEqualTo(2);
assertThat(properties).hasSize(2);
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.FALSE);
}

@Test
public void customAdapter() {
Object adapter = context.getBean("customAdapter");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Expand All @@ -180,25 +182,26 @@ public void testAutoChannel() {
@Test
public void transactionalAdapter() {
Object adapter = context.getBean("transactionalAdapter");
assertThat(adapter.getClass()).isEqualTo(ImapIdleChannelAdapter.class);
assertThat(adapter).isInstanceOf(ImapIdleChannelAdapter.class);
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertThat(adapterAccessor.getPropertyValue("outputChannel")).isSameAs(channel);
assertThat(adapterAccessor.getPropertyValue("autoStartup")).isEqualTo(Boolean.FALSE);
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertThat(receiver.getClass()).isEqualTo(ImapMailReceiver.class);
assertThat(receiver).isInstanceOf(ImapMailReceiver.class);
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertThat(url).isEqualTo(new URLName("imap:foo"));
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
// mail.imap(s).peek properties
assertThat(properties.size()).isEqualTo(2);
assertThat(properties).hasSize(2);
assertThat(receiverAccessor.getPropertyValue("shouldDeleteMessages")).isEqualTo(Boolean.TRUE);
assertThat(receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead")).isEqualTo(Boolean.TRUE);
assertThat(adapterAccessor.getPropertyValue("errorChannel")).isNull();
assertThat(adapterAccessor.getPropertyValue("sendingTaskExecutor")).isEqualTo(context.getBean("executor"));
assertThat(adapterAccessor.getPropertyValue("adviceChain")).isNotNull();
}

public static class TestSearchTermStrategy implements SearchTermStrategy {

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

}

}
12 changes: 8 additions & 4 deletions src/reference/asciidoc/mail.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ By default, the `ImapMailReceiver` searches for messages based on the default `S

The custom user flag is `spring-integration-mail-adapter`, but you can configure it.
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.
`SearchTermStrategy` is a strategy interface with a single method that lets you create an instance of the `SearchTerm` used by the `ImapMailReceiver`.
A `SearchTermStrategy` is a strategy interface with a single method that lets you create an instance of the `SearchTerm` used by the `ImapMailReceiver`.
The following listing shows the `SearchTermStrategy` interface:

====
Expand Down Expand Up @@ -417,8 +417,7 @@ IMPORTANT: In both configurations, `channel` and `should-delete-messages` are re
You should understand why `should-delete-messages` is required.
The issue is with the POP3 protocol, which does not have any knowledge of messages that were read.
It can only know what has been read within a single session.
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.
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.
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.
That is the nature of POP3.
Some might argue that `should-delete-messages` should be `true` by default.
Expand All @@ -434,6 +433,10 @@ Leaving it up to you also means that you are less likely to end up with unintend
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.
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.

In the case of silently dropped connection, an idle cancel task is run in the background periodically (a new IDLE will usually immediately be processed).
To control this interval, a `cancelIdleInterval` option is provided; default 120 (2 minutes).
RFC 2177 recommends an interval no larger than 29 minutes.

[IMPORTANT]
=====
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.
Expand Down Expand Up @@ -502,7 +505,8 @@ In the preceding example, thanks to the `mail-filter-expression` attribute, only

Another reasonable question is what happens on the next poll or idle event or what happens when such an adapter is restarted.
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?
Would they go through the filtering logic again on the next poll or idle? After all, they were not marked as `SEEN`.
Would they go through the filtering logic again on the next poll or idle?
After all, they were not marked as `SEEN`.
The answer is no.
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.
Folder implementations set this flag to indicate that this message is new to this folder.
Expand Down