diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java index 4d73d307525..92a0030b0fa 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java @@ -162,6 +162,9 @@ public final Message receive() { @SuppressWarnings("unchecked") protected Message buildMessage(Object result) { + if (result == null) { + return null; + } Message message = null; Map headers = evaluateHeaders(); if (result instanceof AbstractIntegrationMessageBuilder) { @@ -181,14 +184,14 @@ else if (result instanceof Message) { .build(); } } - else if (result != null) { + else { message = getMessageBuilderFactory() .withPayload(result) .copyHeaders(headers) .build(); } - if (this.countsEnabled && message != null) { + if (this.countsEnabled) { if (this.metricsCaptor != null) { incrementReceiveCounter(); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java index b4b411534ed..763c80930c8 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java @@ -17,6 +17,7 @@ package org.springframework.integration.mail; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Date; @@ -43,9 +44,11 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.mapping.HeaderMapper; +import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; @@ -103,6 +106,8 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl private boolean simpleContent; + private boolean autoCloseFolder = true; + private volatile Store store; private volatile Folder folder; @@ -266,6 +271,22 @@ public void setSimpleContent(boolean simpleContent) { this.simpleContent = simpleContent; } + /** + * Configure a {@code boolean} flag to close the folder automatically after a fetch (default) or + * populate an additional {@link IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} message header instead. + * It is the downstream flow's responsibility to obtain this header and call its {@code close()} whenever + * it is necessary. + *

Keeping the folder open is useful in cases where communication with the server is needed + * when parsing multipart content of the email with attachments. + *

The {@link #setSimpleContent(boolean)} and {@link #setHeaderMapper(HeaderMapper)} options are not + * affected by this flag. + * @param autoCloseFolder {@code false} do not close the folder automatically after a fetch. + * @since 5.2 + */ + public void setAutoCloseFolder(boolean autoCloseFolder) { + this.autoCloseFolder = autoCloseFolder; + } + protected Folder getFolder() { return this.folder; } @@ -357,7 +378,9 @@ public Object[] receive() throws javax.mail.MessagingException { return convertMessagesIfNecessary(searchAndFilterMessages()); } finally { - MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages); + if (this.autoCloseFolder) { + closeFolder(); + } } } finally { @@ -367,6 +390,16 @@ public Object[] receive() throws javax.mail.MessagingException { } } + private void closeFolder() { + this.folderReadLock.lock(); + try { + MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages); + } + finally { + this.folderReadLock.unlock(); + } + } + private MimeMessage[] searchAndFilterMessages() throws MessagingException { if (this.logger.isInfoEnabled()) { this.logger.info("attempting to receive mail from folder [" + this.folder.getFullName() + "]"); @@ -395,17 +428,26 @@ private MimeMessage[] searchAndFilterMessages() throws MessagingException { } private Object[] convertMessagesIfNecessary(MimeMessage[] filteredMessages) { - if (this.headerMapper != null) { + if (this.headerMapper != null || !this.autoCloseFolder) { org.springframework.messaging.Message[] converted = new org.springframework.messaging.Message[filteredMessages.length]; int n = 0; for (MimeMessage message : filteredMessages) { - Map headers = this.headerMapper.toHeaders(message); - converted[n++] = + Object payload = message; + Map headers = null; + if (this.headerMapper != null) { + headers = this.headerMapper.toHeaders(message); + payload = extractContent(message, headers); + } + AbstractIntegrationMessageBuilder messageBuilder = getMessageBuilderFactory() - .withPayload(extractContent(message, headers)) - .copyHeaders(headers) - .build(); + .withPayload(payload) + .copyHeaders(headers); + if (!this.autoCloseFolder) { + messageBuilder.setHeader(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, + (Closeable) this::closeFolder); + } + converted[n++] = messageBuilder.build(); } return converted; } @@ -566,7 +608,7 @@ protected void setAdditionalFlags(Message message) throws MessagingException { public void destroy() { this.folderWriteLock.lock(); try { - MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages); + closeFolder(); MailTransportUtils.closeService(this.store); this.folder = null; this.store = null; @@ -628,11 +670,16 @@ private final class IntegrationMimeMessage extends MimeMessage { @Override public Folder getFolder() { - try { - return obtainFolderInstance(); + if (!AbstractMailReceiver.this.autoCloseFolder) { + return AbstractMailReceiver.this.folder; } - catch (MessagingException e) { - throw new org.springframework.messaging.MessagingException("Unable to obtain the mail folder", e); + else { + try { + return obtainFolderInstance(); + } + catch (MessagingException e) { + throw new org.springframework.messaging.MessagingException("Unable to obtain the mail folder", e); + } } } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java index 945ee094101..d87f4941ffc 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java @@ -21,14 +21,12 @@ import java.util.concurrent.ConcurrentLinkedQueue; import org.springframework.integration.endpoint.AbstractMessageSource; -import org.springframework.messaging.Message; import org.springframework.messaging.MessagingException; import org.springframework.util.Assert; /** * {@link org.springframework.integration.core.MessageSource} implementation that - * delegates to a - * {@link MailReceiver} to poll a mailbox. Each poll of the mailbox may + * delegates to a {@link MailReceiver} to poll a mailbox. Each poll of the mailbox may * return more than one message which will then be stored in a queue. * * @author Jonas Partner @@ -69,12 +67,7 @@ protected Object doReceive() { if (this.logger.isDebugEnabled()) { this.logger.debug("received mail message [" + mailMessage + "]"); } - if (mailMessage instanceof Message) { - return getMessageBuilderFactory().fromMessage((Message) mailMessage); - } - else { - return getMessageBuilderFactory().withPayload(mailMessage); - } + return mailMessage; } } catch (Exception e) { 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 617e605f7e2..c6e290cbffe 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 @@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.AbstractChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.mail.ImapIdleChannelAdapter; @@ -37,6 +38,7 @@ * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell + * @author Artem Bilan */ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser { @@ -52,7 +54,8 @@ protected AbstractBeanDefinition doParse(Element element, ParserContext parserCo IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, txElement, "synchronization-factory", "transactionSynchronizationFactory"); } - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor", "sendingTaskExecutor"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor", + "sendingTaskExecutor"); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(null, DomUtils.getChildElementByTagName(element, "transactional"), beanDefinition, parserContext); @@ -90,15 +93,15 @@ private BeanDefinition parseImapMailReceiver(Element element, ParserContext pars String selectorExpression = element.getAttribute("mail-filter-expression"); - RootBeanDefinition expressionDef = null; if (StringUtils.hasText(selectorExpression)) { - expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean"); + RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression); receiverBuilder.addPropertyValue("selectorExpression", expressionDef); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper"); IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes"); IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder"); return receiverBuilder.getBeanDefinition(); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java index 0f0ba9c6510..7417a000e01 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailInboundChannelAdapterParser.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.mail.MailReceivingMessageSource; @@ -36,6 +37,7 @@ * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell + * @author Artem Bilan */ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { @@ -87,15 +89,15 @@ private BeanDefinition parseMailReceiver(Element element, ParserContext parserCo String selectorExpression = element.getAttribute("mail-filter-expression"); - RootBeanDefinition expressionDef = null; if (StringUtils.hasText(selectorExpression)) { - expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean"); + RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class); expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression); receiverBuilder.addPropertyValue("selectorExpression", expressionDef); } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper"); IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes"); IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder"); return receiverBuilder.getBeanDefinition(); } diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java index 04f3273ed7d..ce6ec72fcd6 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/config/MailReceiverFactoryBean.java @@ -81,6 +81,8 @@ public class MailReceiverFactoryBean extends AbstractFactoryBean { private Boolean simpleContent; + private Boolean autoCloseFolder; + public void setStoreUri(@Nullable String storeUri) { this.storeUri = storeUri; } @@ -141,6 +143,10 @@ public void setSimpleContent(@Nullable Boolean simpleContent) { this.simpleContent = simpleContent; } + public void setAutoCloseFolder(@Nullable Boolean autoCloseFolder) { + this.autoCloseFolder = autoCloseFolder; + } + @Override protected MailReceiver createInstance() { if (this.receiver == null) { @@ -211,6 +217,9 @@ private MailReceiver createReceiver() { // NOSONAR if (this.simpleContent != null) { mailReceiver.setSimpleContent(this.simpleContent); } + if (this.autoCloseFolder != null) { + mailReceiver.setAutoCloseFolder(this.autoCloseFolder); + } mailReceiver.afterPropertiesSet(); return mailReceiver; } 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 873930e78d1..a848af32c28 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 @@ -269,6 +269,21 @@ public ImapIdleChannelAdapterSpec embeddedPartsAsBytes(boolean embeddedPartsAsBy return _this(); } + /** + * When configured to {@code false}, the folder is not closed automatically after a fetch. + * It is the target application's responsibility to close it using the + * {@link org.springframework.integration.IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} header + * from the message produced by this channel adapter. + * @param autoCloseFolder set to {@code false} to keep folder opened. + * @return the spec. + * @since 5.2 + * @see ImapMailReceiver#setAutoCloseFolder(boolean) + */ + public ImapIdleChannelAdapterSpec autoCloseFolder(boolean autoCloseFolder) { + assertReceiver(); + this.receiver.setAutoCloseFolder(autoCloseFolder); + return _this(); + } /** * Configure a {@link TransactionSynchronizationFactory}. Usually used to synchronize diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/MailInboundChannelAdapterSpec.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/MailInboundChannelAdapterSpec.java index ac2570b9300..dfb06d30119 100644 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/MailInboundChannelAdapterSpec.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/dsl/MailInboundChannelAdapterSpec.java @@ -253,6 +253,22 @@ public S simpleContent(boolean simpleContent) { return _this(); } + /** + * When configured to {@code false}, the folder is not closed automatically after a fetch. + * It is the target application's responsibility to close it using the + * {@link org.springframework.integration.IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} header + * from the message produced by this channel adapter. + * @param autoCloseFolder set to {@code false} to keep folder opened. + * @return the spec. + * @since 5.2 + * @see AbstractMailReceiver#setAutoCloseFolder(boolean) + */ + public S autoCloseFolder(boolean autoCloseFolder) { + assertReceiver(); + this.receiver.setAutoCloseFolder(autoCloseFolder); + return _this(); + } + @Override public Map getComponentsToRegister() { return Collections.singletonMap(this.receiver, this.receiver.getComponentName()); 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 534463575e9..6638aef6302 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 @@ -416,6 +416,16 @@ + + + + Default 'true' - close folder automatically after fetch, before producing messages. + + + + + + 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 11734da854a..65378768228 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 @@ -38,6 +38,7 @@ header-mapper="mapper" simple-content="true" embedded-parts-as-bytes="false" + auto-close-folder="false" should-delete-messages="true"/> 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 3c3906edaa7..e4c3ef38380 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 @@ -84,6 +84,7 @@ public void simpleAdapter() { assertThat(receiverAccessor.getPropertyValue("embeddedPartsAsBytes")).isEqualTo(Boolean.FALSE); assertThat(receiverAccessor.getPropertyValue("headerMapper")).isNotNull(); assertThat(receiverAccessor.getPropertyValue("simpleContent")).isEqualTo(Boolean.TRUE); + assertThat(receiverAccessor.getPropertyValue("autoCloseFolder")).isEqualTo(false); } @Test diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests-context.xml b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests-context.xml index 32826e81bda..50435f66507 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests-context.xml +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/InboundChannelAdapterParserTests-context.xml @@ -16,6 +16,7 @@ message = this.pop3Channel.receive(10000); assertThat(message).isNotNull(); MessageHeaders headers = message.getHeaders(); - assertThat(headers.get(MailHeaders.TO, String[].class)[0]).isEqualTo("Foo "); + assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar "); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); assertThat(message.getPayload()).isEqualTo("foo\r\n\r\n"); + assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); + message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class).close(); } @Test @@ -163,6 +166,8 @@ public void testImap() throws Exception { assertThat(mm.getFrom()[0].toString()).isEqualTo("Bar "); assertThat(mm.getSubject()).isEqualTo("Test Email"); assertThat(mm.getContent()).isEqualTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n"); + assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); + message.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, Closeable.class).close(); } @Test @@ -170,10 +175,11 @@ public void testImapIdle() { Message message = this.imapIdleChannel.receive(10000); assertThat(message).isNotNull(); MessageHeaders headers = message.getHeaders(); - assertThat(headers.get(MailHeaders.TO, String[].class)[0]).isEqualTo("Foo "); + assertThat(headers.get(MailHeaders.TO, String[].class)).containsExactly("Foo "); assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar "); assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email"); assertThat(message.getPayload()).isEqualTo(TestMailServer.MailServer.MailHandler.MESSAGE + "\r\n"); + assertThat(message.getHeaders().containsKey(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE)).isTrue(); this.imapIdleAdapter.stop(); assertThat(TestUtils.getPropertyValue(this.imapIdleAdapter, "shouldReconnectAutomatically", Boolean.class)) .isFalse(); @@ -204,6 +210,7 @@ public IntegrationFlow pop3MailFlow() { return IntegrationFlows .from(Mail.pop3InboundAdapter("localhost", pop3Server.getPort(), "user", "pw") .javaMailProperties(p -> p.put("mail.debug", "false")) + .autoCloseFolder(false) .headerMapper(mailHeaderMapper()), e -> e.autoStartup(true).poller(p -> p.fixedDelay(1000))) .enrichHeaders(s -> s.headerExpressions(c -> c.put(MailHeaders.SUBJECT, "payload.subject") @@ -218,6 +225,7 @@ public IntegrationFlow imapMailFlow() { .from(Mail.imapInboundAdapter("imap://user:pw@localhost:" + imapServer.getPort() + "/INBOX") .searchTermStrategy(this::fromAndNotSeenTerm) .userFlag("testSIUserFlag") + .autoCloseFolder(false) .simpleContent(true) .javaMailProperties(p -> p.put("mail.debug", "false")), e -> e.autoStartup(true) @@ -230,13 +238,14 @@ public IntegrationFlow imapMailFlow() { public IntegrationFlow imapIdleFlow() { return IntegrationFlows .from(Mail.imapIdleAdapter("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX") - .autoStartup(true) - .searchTermStrategy(this::fromAndNotSeenTerm) - .userFlag("testSIUserFlag") - .javaMailProperties(p -> p.put("mail.debug", "false") - .put("mail.imap.connectionpoolsize", "5")) - .shouldReconnectAutomatically(false) - .headerMapper(mailHeaderMapper())) + .autoStartup(true) + .searchTermStrategy(this::fromAndNotSeenTerm) + .userFlag("testSIUserFlag") + .autoCloseFolder(false) + .javaMailProperties(p -> p.put("mail.debug", "false") + .put("mail.imap.connectionpoolsize", "5")) + .shouldReconnectAutomatically(false) + .headerMapper(mailHeaderMapper())) .channel(MessageChannels.queue("imapIdleChannel")) .get(); } diff --git a/src/reference/asciidoc/ftp.adoc b/src/reference/asciidoc/ftp.adoc index 23a1bea643e..1b195902197 100644 --- a/src/reference/asciidoc/ftp.adoc +++ b/src/reference/asciidoc/ftp.adoc @@ -596,7 +596,7 @@ consumed. The session is provided in the `closeableResource` header (`IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE`). Standard framework components, such as the `FileSplitter` and `StreamTransformer`, automatically close the session. See <> and <> for more information about these components. -The follwoing example shows how to configure an `inbound-streaming-channel-adapter`: +The following example shows how to configure an `inbound-streaming-channel-adapter`: ==== [source, xml] diff --git a/src/reference/asciidoc/mail.adoc b/src/reference/asciidoc/mail.adoc index e6e88cfc4c4..9c321dc0660 100644 --- a/src/reference/asciidoc/mail.adoc +++ b/src/reference/asciidoc/mail.adoc @@ -111,11 +111,30 @@ Starting with version 2.2, the framework eagerly fetches IMAP messages and expos This had the undesired side effect of changing the `getContent()` behavior. This inconsistency was further exacerbated by the <> enhancement introduced in version 4.3, because, when a header mapper was provided, the payload was rendered by the `IMAPMessage.getContent()` method. This meant that the IMAP content differed, depending on whether or not a header mapper was provided. + Starting with version 5.0, messages originating from an IMAP source render the content in accordance with `IMAPMessage.getContent()` behavior, regardless of whether a header mapper is provided. If you do not use a header mapper and you wish to revert to the previous behavior of rendering only the body, set the `simpleContent` boolean property on the mail receiver to `true`. This property now controls the rendering regardless of whether a header mapper is used. It now allows body-only rendering when a header mapper is provided. +Starting with version 5.2, the `autoCloseFolder` option is provided on the mail receiver. +Setting it to `false` doesn't close the folder automatically after a fetch, but instead an `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` header (see <> for more information) is populated into every message to producer from the channel adapter. +It is the target application's responsibility to call the `close()` on this header whenever it is necessary in the downstream flow: + +==== +[source,java] +---- +Closeable closeableResource = StaticMessageHeaderAccessor.getCloseableResource(mailMessage); +if (closeableResource != null) { + closeableResource.close(); +} + +---- +==== + +Keeping the folder open is useful in cases where communication with the server is needed during parsing multipart content of the email with attachments. +The `close()` on the `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` header delegates to the `AbstractMailReceiver` to close the folder with `expunge` option if `shouldDeleteMessages` is configured respectively on the `AbstractMailReceiver`. + [[mail-mapping]] === Inbound Mail Message Mapping diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 2effb32a7e8..66b815d3a11 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -39,3 +39,9 @@ See <> for more information. When using a `TcpNioServerConnectionFactory`, priority is now given to accepting new connections over reading from existing connections, but it is configurable. See <> for more information. + +[[x5.2-mail]] +==== Mail Changes + +The `AbstractMailReceiver` has now an `autoCloseFolder` option (`true` by default), to disable an automatic folder close after a fetch, but populate `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` header instead for downstream interaction. +See <> for more information.