Skip to content

INT-4299: Add AbstractMailReceiver.autoCloseFolder #2887

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

Merged
merged 2 commits into from
Apr 9, 2019
Merged
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 @@ -162,6 +162,9 @@ public final Message<T> receive() {

@SuppressWarnings("unchecked")
protected Message<T> buildMessage(Object result) {
if (result == null) {
return null;
}
Message<?> message = null;
Map<String, Object> headers = evaluateHeaders();
if (result instanceof AbstractIntegrationMessageBuilder<?>) {
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p> Keeping the folder open is useful in cases where communication with the server is needed
* when parsing multipart content of the email with attachments.
* <p> 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;
}
Expand Down Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to close here if no messages were retrieved (needs a test),

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Why do we need to close the folder at all?
I don't see any comment in the Internet requiring to close the folder after each fetch.
😕

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}
}
}
finally {
Expand All @@ -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() + "]");
Expand Down Expand Up @@ -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<String, Object> headers = this.headerMapper.toHeaders(message);
converted[n++] =
Object payload = message;
Map<String, Object> headers = null;
if (this.headerMapper != null) {
headers = this.headerMapper.toHeaders(message);
payload = extractContent(message, headers);
}
AbstractIntegrationMessageBuilder<Object> 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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +38,7 @@
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {

Expand All @@ -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);
Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,6 +37,7 @@
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class MailInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {

Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public class MailReceiverFactoryBean extends AbstractFactoryBean<MailReceiver> {

private Boolean simpleContent;

private Boolean autoCloseFolder;

public void setStoreUri(@Nullable String storeUri) {
this.storeUri = storeUri;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object, String> getComponentsToRegister() {
return Collections.singletonMap(this.receiver, this.receiver.getComponentName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,16 @@
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="auto-close-folder" default="true">
<xsd:annotation>
<xsd:documentation>
Default 'true' - close folder automatically after fetch, before producing messages.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>

</xsd:schema>
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
header-mapper="mapper"
simple-content="true"
embedded-parts-as-bytes="false"
auto-close-folder="false"
should-delete-messages="true"/>

<bean id="mapper" class="org.springframework.integration.mail.support.DefaultMailHeaderMapper" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<mail:inbound-channel-adapter id="pop3ShouldDeleteTrue"
store-uri="pop3:test" channel="testChannel" should-delete-messages="true"
header-mapper="mapper" embedded-parts-as-bytes="false"
auto-close-folder="false"
auto-startup="false" />

<mail:inbound-channel-adapter id="pop3ShouldDeleteFalse"
Expand Down
Loading