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()`

* Fix language in the Doc
This commit is contained in:
Artem Bilan
2019-08-15 17:55:51 -04:00
committed by Gary Russell
parent 6d0757a08a
commit ca8377da9b
7 changed files with 62 additions and 30 deletions

View File

@@ -102,7 +102,7 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {
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();
}
}

View File

@@ -368,6 +368,20 @@ public class ImapIdleChannelAdapterSpec
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;

View File

@@ -24,6 +24,7 @@ import org.springframework.integration.mail.SearchTermStrategy;
*
* @author Gary Russell
* @author Artem Bilan
*
* @since 5.0
*/
public class ImapMailInboundChannelAdapterSpec
@@ -72,12 +73,13 @@ public class ImapMailInboundChannelAdapterSpec
* @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;
}
}

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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 class ImapIdleChannelAdapterParserTests {
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 class ImapIdleChannelAdapterParserTests {
}
@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 class ImapIdleChannelAdapterParserTests {
@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 class ImapIdleChannelAdapterParserTests {
@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 class ImapIdleChannelAdapterParserTests {
}
}
}

View File

@@ -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 the case of a 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.
@@ -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.
@@ -626,4 +630,3 @@ public class MailApplication {
}
----
====