From 20eaec9cfc7806cb69c83a0f39564a92ca1d9816 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 15 Aug 2019 17:55:51 -0400 Subject: [PATCH 1/2] GH-3017: Expose cancel-idle-interval XML attribute Fixes https://github.com/spring-projects/spring-integration/issues/3017 * Expose also a `ImapIdleChannelAdapterSpec.cancelIdleInterval()` option * Deprecate useless `ImapMailInboundChannelAdapterSpec.cancelIdleInterval()` --- .../config/ImapIdleChannelAdapterParser.java | 2 +- .../mail/dsl/ImapIdleChannelAdapterSpec.java | 14 ++++++ .../ImapMailInboundChannelAdapterSpec.java | 4 +- .../config/spring-integration-mail-5.2.xsd | 8 ++++ ...pIdleChannelAdapterParserTests-context.xml | 3 +- .../ImapIdleChannelAdapterParserTests.java | 48 ++++++++++--------- src/reference/asciidoc/mail.adoc | 12 +++-- 7 files changed, 62 insertions(+), 29 deletions(-) diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java index c6e290cbffe..68b7fcd6d17 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParser.java @@ -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(); } } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapIdleChannelAdapterSpec.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapIdleChannelAdapterSpec.java index a848af32c28..d61ceccb5e3 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapIdleChannelAdapterSpec.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapIdleChannelAdapterSpec.java @@ -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 getComponentsToRegister() { return this.componentsToRegister; diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapMailInboundChannelAdapterSpec.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapMailInboundChannelAdapterSpec.java index c8bffb292df..e1efeb78b5f 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapMailInboundChannelAdapterSpec.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/ImapMailInboundChannelAdapterSpec.java @@ -24,6 +24,7 @@ * * @author Gary Russell * @author Artem Bilan + * * @since 5.0 */ public class ImapMailInboundChannelAdapterSpec @@ -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; } - } diff --git a/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-5.2.xsd b/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-5.2.xsd index 6638aef6302..033f49e4791 100644 --- a/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-5.2.xsd +++ b/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-5.2.xsd @@ -171,6 +171,14 @@ + + + + How often to recycle the idle task (in case of a silently dropped connection). + Seconds; default 120 (2 minutes). + + + diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml index 65378768228..b385e0ae8cf 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml @@ -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"/> diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests.java index e4c3ef38380..8641325bdf4 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests.java @@ -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(); @@ -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")); @@ -112,42 +114,42 @@ 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); } @@ -155,13 +157,13 @@ public void simpleAdapterWithMarkeMessagesAsReadFalse() { @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")); @@ -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 @@ -207,4 +210,5 @@ public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) { } } + } diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index 500040c9177..01518d8a3d8 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -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: ==== @@ -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. @@ -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 case of silently dropped connection, an idle cancel task is run on 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. @@ -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. From 8497fd830267f0c984f4ac183c74571c6af7a293 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 28 Aug 2019 16:29:10 -0400 Subject: [PATCH 2/2] * Fix language in the Doc --- src/reference/asciidoc/mail.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index 01518d8a3d8..aad4712c6a9 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -433,7 +433,7 @@ 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 case of silently dropped connection, an idle cancel task is run on background periodically (a new IDLE will usually immediately be processed). +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.