Skip to content

Commit c05876e

Browse files
artembilangaryrussell
authored andcommitted
INT-4299: Add AbstractMailReceiver.autoCloseFolder (#2887)
* INT-4299: Add AbstractMailReceiver.autoCloseFolder JIRA: https://jira.spring.io/browse/INT-4299 * Optimize `AbstractMessageSource` do not evaluate headers when polling result is `null` * Optimize `MailReceivingMessageSource` do not wrap polling result to the message: the `AbstractMessageSource` will do that later * * Fix JavaDocs * Document `autoCloseFolder` option
1 parent dc88bb4 commit c05876e

File tree

17 files changed

+179
-43
lines changed

17 files changed

+179
-43
lines changed

spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ public final Message<T> receive() {
162162

163163
@SuppressWarnings("unchecked")
164164
protected Message<T> buildMessage(Object result) {
165+
if (result == null) {
166+
return null;
167+
}
165168
Message<?> message = null;
166169
Map<String, Object> headers = evaluateHeaders();
167170
if (result instanceof AbstractIntegrationMessageBuilder<?>) {
@@ -181,14 +184,14 @@ else if (result instanceof Message<?>) {
181184
.build();
182185
}
183186
}
184-
else if (result != null) {
187+
else {
185188
message =
186189
getMessageBuilderFactory()
187190
.withPayload(result)
188191
.copyHeaders(headers)
189192
.build();
190193
}
191-
if (this.countsEnabled && message != null) {
194+
if (this.countsEnabled) {
192195
if (this.metricsCaptor != null) {
193196
incrementReceiveCounter();
194197
}

spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java

+59-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.integration.mail;
1818

1919
import java.io.ByteArrayOutputStream;
20+
import java.io.Closeable;
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.util.Date;
@@ -43,9 +44,11 @@
4344
import org.springframework.beans.factory.DisposableBean;
4445
import org.springframework.expression.Expression;
4546
import org.springframework.expression.spel.support.StandardEvaluationContext;
47+
import org.springframework.integration.IntegrationMessageHeaderAccessor;
4648
import org.springframework.integration.context.IntegrationObjectSupport;
4749
import org.springframework.integration.expression.ExpressionUtils;
4850
import org.springframework.integration.mapping.HeaderMapper;
51+
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
4952
import org.springframework.messaging.MessageHeaders;
5053
import org.springframework.util.Assert;
5154
import org.springframework.util.FileCopyUtils;
@@ -103,6 +106,8 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
103106

104107
private boolean simpleContent;
105108

109+
private boolean autoCloseFolder = true;
110+
106111
private volatile Store store;
107112

108113
private volatile Folder folder;
@@ -266,6 +271,22 @@ public void setSimpleContent(boolean simpleContent) {
266271
this.simpleContent = simpleContent;
267272
}
268273

274+
/**
275+
* Configure a {@code boolean} flag to close the folder automatically after a fetch (default) or
276+
* populate an additional {@link IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} message header instead.
277+
* It is the downstream flow's responsibility to obtain this header and call its {@code close()} whenever
278+
* it is necessary.
279+
* <p> Keeping the folder open is useful in cases where communication with the server is needed
280+
* when parsing multipart content of the email with attachments.
281+
* <p> The {@link #setSimpleContent(boolean)} and {@link #setHeaderMapper(HeaderMapper)} options are not
282+
* affected by this flag.
283+
* @param autoCloseFolder {@code false} do not close the folder automatically after a fetch.
284+
* @since 5.2
285+
*/
286+
public void setAutoCloseFolder(boolean autoCloseFolder) {
287+
this.autoCloseFolder = autoCloseFolder;
288+
}
289+
269290
protected Folder getFolder() {
270291
return this.folder;
271292
}
@@ -357,7 +378,9 @@ public Object[] receive() throws javax.mail.MessagingException {
357378
return convertMessagesIfNecessary(searchAndFilterMessages());
358379
}
359380
finally {
360-
MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
381+
if (this.autoCloseFolder) {
382+
closeFolder();
383+
}
361384
}
362385
}
363386
finally {
@@ -367,6 +390,16 @@ public Object[] receive() throws javax.mail.MessagingException {
367390
}
368391
}
369392

393+
private void closeFolder() {
394+
this.folderReadLock.lock();
395+
try {
396+
MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
397+
}
398+
finally {
399+
this.folderReadLock.unlock();
400+
}
401+
}
402+
370403
private MimeMessage[] searchAndFilterMessages() throws MessagingException {
371404
if (this.logger.isInfoEnabled()) {
372405
this.logger.info("attempting to receive mail from folder [" + this.folder.getFullName() + "]");
@@ -395,17 +428,26 @@ private MimeMessage[] searchAndFilterMessages() throws MessagingException {
395428
}
396429

397430
private Object[] convertMessagesIfNecessary(MimeMessage[] filteredMessages) {
398-
if (this.headerMapper != null) {
431+
if (this.headerMapper != null || !this.autoCloseFolder) {
399432
org.springframework.messaging.Message<?>[] converted =
400433
new org.springframework.messaging.Message<?>[filteredMessages.length];
401434
int n = 0;
402435
for (MimeMessage message : filteredMessages) {
403-
Map<String, Object> headers = this.headerMapper.toHeaders(message);
404-
converted[n++] =
436+
Object payload = message;
437+
Map<String, Object> headers = null;
438+
if (this.headerMapper != null) {
439+
headers = this.headerMapper.toHeaders(message);
440+
payload = extractContent(message, headers);
441+
}
442+
AbstractIntegrationMessageBuilder<Object> messageBuilder =
405443
getMessageBuilderFactory()
406-
.withPayload(extractContent(message, headers))
407-
.copyHeaders(headers)
408-
.build();
444+
.withPayload(payload)
445+
.copyHeaders(headers);
446+
if (!this.autoCloseFolder) {
447+
messageBuilder.setHeader(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE,
448+
(Closeable) this::closeFolder);
449+
}
450+
converted[n++] = messageBuilder.build();
409451
}
410452
return converted;
411453
}
@@ -566,7 +608,7 @@ protected void setAdditionalFlags(Message message) throws MessagingException {
566608
public void destroy() {
567609
this.folderWriteLock.lock();
568610
try {
569-
MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
611+
closeFolder();
570612
MailTransportUtils.closeService(this.store);
571613
this.folder = null;
572614
this.store = null;
@@ -628,11 +670,16 @@ private final class IntegrationMimeMessage extends MimeMessage {
628670

629671
@Override
630672
public Folder getFolder() {
631-
try {
632-
return obtainFolderInstance();
673+
if (!AbstractMailReceiver.this.autoCloseFolder) {
674+
return AbstractMailReceiver.this.folder;
633675
}
634-
catch (MessagingException e) {
635-
throw new org.springframework.messaging.MessagingException("Unable to obtain the mail folder", e);
676+
else {
677+
try {
678+
return obtainFolderInstance();
679+
}
680+
catch (MessagingException e) {
681+
throw new org.springframework.messaging.MessagingException("Unable to obtain the mail folder", e);
682+
}
636683
}
637684
}
638685

spring-integration-mail/src/main/java/org/springframework/integration/mail/MailReceivingMessageSource.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@
2121
import java.util.concurrent.ConcurrentLinkedQueue;
2222

2323
import org.springframework.integration.endpoint.AbstractMessageSource;
24-
import org.springframework.messaging.Message;
2524
import org.springframework.messaging.MessagingException;
2625
import org.springframework.util.Assert;
2726

2827
/**
2928
* {@link org.springframework.integration.core.MessageSource} implementation that
30-
* delegates to a
31-
* {@link MailReceiver} to poll a mailbox. Each poll of the mailbox may
29+
* delegates to a {@link MailReceiver} to poll a mailbox. Each poll of the mailbox may
3230
* return more than one message which will then be stored in a queue.
3331
*
3432
* @author Jonas Partner
@@ -69,12 +67,7 @@ protected Object doReceive() {
6967
if (this.logger.isDebugEnabled()) {
7068
this.logger.debug("received mail message [" + mailMessage + "]");
7169
}
72-
if (mailMessage instanceof Message) {
73-
return getMessageBuilderFactory().fromMessage((Message<?>) mailMessage);
74-
}
75-
else {
76-
return getMessageBuilderFactory().withPayload(mailMessage);
77-
}
70+
return mailMessage;
7871
}
7972
}
8073
catch (Exception e) {

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2424
import org.springframework.beans.factory.support.RootBeanDefinition;
2525
import org.springframework.beans.factory.xml.ParserContext;
26+
import org.springframework.integration.config.ExpressionFactoryBean;
2627
import org.springframework.integration.config.xml.AbstractChannelAdapterParser;
2728
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
2829
import org.springframework.integration.mail.ImapIdleChannelAdapter;
@@ -37,6 +38,7 @@
3738
* @author Mark Fisher
3839
* @author Oleg Zhurakousky
3940
* @author Gary Russell
41+
* @author Artem Bilan
4042
*/
4143
public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {
4244

@@ -52,7 +54,8 @@ protected AbstractBeanDefinition doParse(Element element, ParserContext parserCo
5254
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, txElement,
5355
"synchronization-factory", "transactionSynchronizationFactory");
5456
}
55-
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor", "sendingTaskExecutor");
57+
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor",
58+
"sendingTaskExecutor");
5659
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
5760
IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(null,
5861
DomUtils.getChildElementByTagName(element, "transactional"), beanDefinition, parserContext);
@@ -90,15 +93,15 @@ private BeanDefinition parseImapMailReceiver(Element element, ParserContext pars
9093

9194
String selectorExpression = element.getAttribute("mail-filter-expression");
9295

93-
RootBeanDefinition expressionDef = null;
9496
if (StringUtils.hasText(selectorExpression)) {
95-
expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
97+
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
9698
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression);
9799
receiverBuilder.addPropertyValue("selectorExpression", expressionDef);
98100
}
99101
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper");
100102
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes");
101103
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content");
104+
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder");
102105

103106
return receiverBuilder.getBeanDefinition();
104107
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2424
import org.springframework.beans.factory.support.RootBeanDefinition;
2525
import org.springframework.beans.factory.xml.ParserContext;
26+
import org.springframework.integration.config.ExpressionFactoryBean;
2627
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
2728
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
2829
import org.springframework.integration.mail.MailReceivingMessageSource;
@@ -36,6 +37,7 @@
3637
* @author Mark Fisher
3738
* @author Oleg Zhurakousky
3839
* @author Gary Russell
40+
* @author Artem Bilan
3941
*/
4042
public class MailInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
4143

@@ -87,15 +89,15 @@ private BeanDefinition parseMailReceiver(Element element, ParserContext parserCo
8789

8890
String selectorExpression = element.getAttribute("mail-filter-expression");
8991

90-
RootBeanDefinition expressionDef = null;
9192
if (StringUtils.hasText(selectorExpression)) {
92-
expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
93+
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
9394
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(selectorExpression);
9495
receiverBuilder.addPropertyValue("selectorExpression", expressionDef);
9596
}
9697
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "header-mapper");
9798
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "embedded-parts-as-bytes");
9899
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "simple-content");
100+
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "auto-close-folder");
99101

100102
return receiverBuilder.getBeanDefinition();
101103
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public class MailReceiverFactoryBean extends AbstractFactoryBean<MailReceiver> {
8181

8282
private Boolean simpleContent;
8383

84+
private Boolean autoCloseFolder;
85+
8486
public void setStoreUri(@Nullable String storeUri) {
8587
this.storeUri = storeUri;
8688
}
@@ -141,6 +143,10 @@ public void setSimpleContent(@Nullable Boolean simpleContent) {
141143
this.simpleContent = simpleContent;
142144
}
143145

146+
public void setAutoCloseFolder(@Nullable Boolean autoCloseFolder) {
147+
this.autoCloseFolder = autoCloseFolder;
148+
}
149+
144150
@Override
145151
protected MailReceiver createInstance() {
146152
if (this.receiver == null) {
@@ -211,6 +217,9 @@ private MailReceiver createReceiver() { // NOSONAR
211217
if (this.simpleContent != null) {
212218
mailReceiver.setSimpleContent(this.simpleContent);
213219
}
220+
if (this.autoCloseFolder != null) {
221+
mailReceiver.setAutoCloseFolder(this.autoCloseFolder);
222+
}
214223
mailReceiver.afterPropertiesSet();
215224
return mailReceiver;
216225
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,21 @@ public ImapIdleChannelAdapterSpec embeddedPartsAsBytes(boolean embeddedPartsAsBy
269269
return _this();
270270
}
271271

272+
/**
273+
* When configured to {@code false}, the folder is not closed automatically after a fetch.
274+
* It is the target application's responsibility to close it using the
275+
* {@link org.springframework.integration.IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} header
276+
* from the message produced by this channel adapter.
277+
* @param autoCloseFolder set to {@code false} to keep folder opened.
278+
* @return the spec.
279+
* @since 5.2
280+
* @see ImapMailReceiver#setAutoCloseFolder(boolean)
281+
*/
282+
public ImapIdleChannelAdapterSpec autoCloseFolder(boolean autoCloseFolder) {
283+
assertReceiver();
284+
this.receiver.setAutoCloseFolder(autoCloseFolder);
285+
return _this();
286+
}
272287

273288
/**
274289
* Configure a {@link TransactionSynchronizationFactory}. Usually used to synchronize

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

+16
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,22 @@ public S simpleContent(boolean simpleContent) {
253253
return _this();
254254
}
255255

256+
/**
257+
* When configured to {@code false}, the folder is not closed automatically after a fetch.
258+
* It is the target application's responsibility to close it using the
259+
* {@link org.springframework.integration.IntegrationMessageHeaderAccessor#CLOSEABLE_RESOURCE} header
260+
* from the message produced by this channel adapter.
261+
* @param autoCloseFolder set to {@code false} to keep folder opened.
262+
* @return the spec.
263+
* @since 5.2
264+
* @see AbstractMailReceiver#setAutoCloseFolder(boolean)
265+
*/
266+
public S autoCloseFolder(boolean autoCloseFolder) {
267+
assertReceiver();
268+
this.receiver.setAutoCloseFolder(autoCloseFolder);
269+
return _this();
270+
}
271+
256272
@Override
257273
public Map<Object, String> getComponentsToRegister() {
258274
return Collections.singletonMap(this.receiver, this.receiver.getComponentName());

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

+10
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,16 @@
416416
<xsd:union memberTypes="xsd:boolean xsd:string"/>
417417
</xsd:simpleType>
418418
</xsd:attribute>
419+
<xsd:attribute name="auto-close-folder" default="true">
420+
<xsd:annotation>
421+
<xsd:documentation>
422+
Default 'true' - close folder automatically after fetch, before producing messages.
423+
</xsd:documentation>
424+
</xsd:annotation>
425+
<xsd:simpleType>
426+
<xsd:union memberTypes="xsd:boolean xsd:string"/>
427+
</xsd:simpleType>
428+
</xsd:attribute>
419429
</xsd:attributeGroup>
420430

421431
</xsd:schema>

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

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
header-mapper="mapper"
3939
simple-content="true"
4040
embedded-parts-as-bytes="false"
41+
auto-close-folder="false"
4142
should-delete-messages="true"/>
4243

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

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

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public void simpleAdapter() {
8484
assertThat(receiverAccessor.getPropertyValue("embeddedPartsAsBytes")).isEqualTo(Boolean.FALSE);
8585
assertThat(receiverAccessor.getPropertyValue("headerMapper")).isNotNull();
8686
assertThat(receiverAccessor.getPropertyValue("simpleContent")).isEqualTo(Boolean.TRUE);
87+
assertThat(receiverAccessor.getPropertyValue("autoCloseFolder")).isEqualTo(false);
8788
}
8889

8990
@Test

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

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<mail:inbound-channel-adapter id="pop3ShouldDeleteTrue"
1717
store-uri="pop3:test" channel="testChannel" should-delete-messages="true"
1818
header-mapper="mapper" embedded-parts-as-bytes="false"
19+
auto-close-folder="false"
1920
auto-startup="false" />
2021

2122
<mail:inbound-channel-adapter id="pop3ShouldDeleteFalse"

0 commit comments

Comments
 (0)