INT-3861: Mail Namespace Support for userFlag

JIRA: https://jira.spring.io/browse/INT-3861

Allow the custom `userFlag` to be configured using the namespace.

Polishing according PR comments
This commit is contained in:
Gary Russell
2015-11-17 14:29:58 -05:00
committed by Artem Bilan
parent 33a40f6cae
commit f80c0db0df
11 changed files with 81 additions and 43 deletions

View File

@@ -52,20 +52,15 @@ import org.springframework.util.Assert;
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean{
public final static String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter";
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean {
/**
* Default user flag for marking messages as seen by this receiver:
* {@value #DEFAULT_SI_USER_FLAG}.
* @deprecated - this constant will be removed in 4.3; see
* {@link #setUserFlag(String)} and {@link #DEFAULT_SI_USER_FLAG}
*/
@Deprecated
public final static String SI_USER_FLAG = DEFAULT_SI_USER_FLAG;
public final static String DEFAULT_SI_USER_FLAG = "spring-integration-mail-adapter";
protected final Log logger = LogFactory.getLog(this.getClass());
protected final Log logger = LogFactory.getLog(getClass());
private final URLName url;
@@ -155,7 +150,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
}
protected Properties getJavaMailProperties() {
return javaMailProperties;
return this.javaMailProperties;
}
/**
@@ -197,7 +192,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
}
protected String getUserFlag() {
return userFlag;
return this.userFlag;
}
/**
@@ -286,7 +281,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
if (logger.isInfoEnabled()) {
logger.info("attempting to receive mail from folder [" + this.getFolder().getFullName() + "]");
}
Message[] messages = this.searchForNewMessages();
Message[] messages = searchForNewMessages();
if (this.maxFetchSize > 0 && messages.length > this.maxFetchSize) {
Message[] reducedMessages = new Message[this.maxFetchSize];
System.arraycopy(messages, 0, reducedMessages, 0, this.maxFetchSize);
@@ -296,16 +291,16 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
logger.debug("found " + messages.length + " new messages");
}
if (messages.length > 0) {
this.fetchMessages(messages);
fetchMessages(messages);
}
if (logger.isDebugEnabled()) {
logger.debug("Received " + messages.length + " messages");
}
Message[] filteredMessages = this.filterMessagesThruSelector(messages);
Message[] filteredMessages = filterMessagesThruSelector(messages);
this.postProcessFilteredMessages(filteredMessages);
postProcessFilteredMessages(filteredMessages);
return filteredMessages;
}
@@ -316,10 +311,10 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
}
private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException {
this.setMessageFlags(filteredMessages);
setMessageFlags(filteredMessages);
if (this.shouldDeleteMessages()) {
this.deleteMessages(filteredMessages);
if (shouldDeleteMessages()) {
deleteMessages(filteredMessages);
}
// Copy messages to cause an eager fetch
for (int i = 0; i < filteredMessages.length; i++) {
@@ -331,7 +326,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
private void setMessageFlags(Message[] filteredMessages) throws MessagingException {
boolean recentFlagSupported = false;
Flags flags = this.getFolder().getPermanentFlags();
Flags flags = getFolder().getPermanentFlags();
if (flags != null){
recentFlagSupported = flags.contains(Flags.Flag.RECENT);
@@ -355,7 +350,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
message.setFlag(Flags.Flag.FLAGGED, true);
}
}
this.setAdditionalFlags(message);
setAdditionalFlags(message);
}
}
@@ -438,7 +433,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
protected void onInit() throws Exception {
super.onInit();
this.folderOpenMode = Folder.READ_WRITE;
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
this.initialized = true;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,6 +62,7 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {
private BeanDefinition parseImapMailReceiver(Element element, ParserContext parserContext) {
BeanDefinitionBuilder receiverBuilder = BeanDefinitionBuilder.genericBeanDefinition(ImapMailReceiver.class);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "search-term-strategy");
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "user-flag");
Object source = parserContext.extractSource(element);
String uri = element.getAttribute("store-uri");
if (StringUtils.hasText(uri)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.integration.mail.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -26,7 +28,6 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.mail.MailReceivingMessageSource;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;inbound-channel-adapter&gt; element of Spring Integration's 'mail' namespace.
@@ -52,6 +53,7 @@ public class MailInboundChannelAdapterParser extends AbstractPollingInboundChann
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "store-uri");
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "protocol");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(receiverBuilder, element, "search-term-strategy");
IntegrationNamespaceUtils.setValueIfAttributeDefined(receiverBuilder, element, "user-flag");
String session = element.getAttribute("session");
if (StringUtils.hasText(session)) {
if (element.hasAttribute("java-mail-properties") || element.hasAttribute("authenticator")) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import org.springframework.util.StringUtils;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 1.0.3
*/
public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, DisposableBean, BeanFactoryAware {
@@ -74,6 +75,8 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
private volatile SearchTermStrategy searchTermStrategy;
private volatile String userFlag;
private volatile BeanFactory beanFactory;
public void setStoreUri(String storeUri) {
@@ -120,11 +123,16 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
this.searchTermStrategy = searchTermStrategy;
}
public void setUserFlag(String userFlag) {
this.userFlag = userFlag;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public MailReceiver getObject() throws Exception {
if (this.receiver == null) {
this.receiver = this.createReceiver();
@@ -132,10 +140,12 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
return this.receiver;
}
@Override
public Class<?> getObjectType() {
return (this.receiver != null) ? this.receiver.getClass() : MailReceiver.class;
}
@Override
public boolean isSingleton() {
return true;
}
@@ -165,7 +175,7 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
AbstractMailReceiver receiver = isPop3 ? new Pop3MailReceiver(this.storeUri) : new ImapMailReceiver(this.storeUri);
if (this.session != null) {
Assert.isNull(this.javaMailProperties, "JavaMail Properties are not allowed when a Session has been provided.");
Assert.isNull(this.authenticator, "A JavaMail Authenticator is not allowed when a Session has been provied.");
Assert.isNull(this.authenticator, "A JavaMail Authenticator is not allowed when a Session has been provided.");
receiver.setSession(this.session);
}
if (this.searchTermStrategy != null) {
@@ -185,6 +195,9 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
}
receiver.setMaxFetchSize(this.maxFetchSize);
receiver.setSelectorExpression(selectorExpression);
if (StringUtils.hasText(this.userFlag)) {
receiver.setUserFlag(this.userFlag);
}
if (isPop3) {
if (this.isShouldMarkMessagesAsRead() && this.logger.isWarnEnabled()) {
@@ -201,6 +214,7 @@ public class MailReceiverFactoryBean implements FactoryBean<MailReceiver>, Dispo
return receiver;
}
@Override
public void destroy() throws Exception {
if (this.receiver != null && this.receiver instanceof DisposableBean) {
((DisposableBean) this.receiver).destroy();

View File

@@ -253,6 +253,16 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="user-flag" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify the user flag to mark messages read by the adapter when the mail store does not support
the RECENT flag but does support user flags (keywords). This flag is set regardless of the
'should-mark-messages-as-read' attribute. It can be used in the 'search-term-strategy' (and
is by default) to ignore already processed messages. Default: 'spring-integration-mail-adapter'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="mail-to-string-transformer">

View File

@@ -18,7 +18,7 @@
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/mail
http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd">
<integration:message-history/>
<context:property-placeholder properties-ref="configProperties"/>
@@ -36,21 +36,22 @@
channel="channel"
auto-startup="false"
should-delete-messages="true"/>
<mail:imap-idle-channel-adapter id="simpleAdapterWithErrorChannel"
store-uri="imap:foo"
channel="channel"
error-channel="errorChannel"
auto-startup="false"
should-delete-messages="true"/>
<mail:imap-idle-channel-adapter id="simpleAdapterMarkAsRead"
store-uri="imap:foo"
channel="channel"
auto-startup="false"
should-delete-messages="true"
user-flag="flagged"
should-mark-messages-as-read="true"/>
<mail:imap-idle-channel-adapter id="simpleAdapterMarkAsReadFalse"
store-uri="imap:foo"
channel="channel"
@@ -63,9 +64,9 @@
channel="channel"
auto-startup="false"
java-mail-properties="javaMailProperties"
should-delete-messages="${mail.delete}"
should-delete-messages="${mail.delete}"
search-term-strategy="searchTermStrategy"/>
<mail:imap-idle-channel-adapter id="transactionalAdapter"
store-uri="imap:foo"
channel="channel"
@@ -97,4 +98,4 @@
<integration:bridge input-channel="autoChannel" output-channel="nullChannel" />
</beans>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,15 +30,16 @@ import javax.mail.search.SearchTerm;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.mail.ImapIdleChannelAdapter;
import org.springframework.integration.mail.ImapMailReceiver;
import org.springframework.integration.mail.SearchTermStrategy;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -120,6 +121,7 @@ public class ImapIdleChannelAdapterParserTests {
assertEquals(2, properties.size());
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldDeleteMessages"));
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead"));
assertEquals("flagged", receiverAccessor.getPropertyValue("userFlag"));
}
@Test
@@ -191,6 +193,7 @@ public class ImapIdleChannelAdapterParserTests {
}
public static class TestSearchTermStrategy implements SearchTermStrategy {
@Override
public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
return null;
}

View File

@@ -91,9 +91,9 @@
<mail:inbound-channel-adapter id="imapWithoutStoreUri" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false"/>
<mail:imap-idle-channel-adapter id="imapIdleWithoutStoreUri" channel="testChannel" should-delete-messages="false" auto-startup="false"/>
<mail:inbound-channel-adapter id="pop3ShouldMarkAsReadTrue" channel="testChannel" protocol="pop3" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="true"/>
<mail:inbound-channel-adapter id="pop3ShouldMarkAsReadFalse" channel="testChannel" protocol="pop3" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="false"/>
<mail:inbound-channel-adapter id="imapShouldMarkAsReadTrue" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false" should-mark-messages-as-read="true"/>
@@ -106,7 +106,8 @@
<!-- INT-2800 -->
<mail:inbound-channel-adapter id="imapWithSearch" channel="testChannel" protocol="imap" should-delete-messages="false" auto-startup="false"
<mail:inbound-channel-adapter id="imapWithSearch" channel="testChannel" protocol="imap"
should-delete-messages="false" auto-startup="false" user-flag="flagged"
search-term-strategy="searchTermStrategy"/>
<bean id="searchTermStrategy" class="org.mockito.Mockito" factory-method="mock">

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.mail.AbstractMailReceiver;
import org.springframework.integration.mail.ImapIdleChannelAdapter;
@@ -45,12 +44,14 @@ import org.springframework.integration.mail.ImapMailReceiver;
import org.springframework.integration.mail.Pop3MailReceiver;
import org.springframework.integration.mail.SearchTermStrategy;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 1.0.5
*/
@ContextConfiguration
@@ -278,7 +279,8 @@ public class InboundChannelAdapterParserTests {
public void inboundChannelAdapterRequiresShouldDeleteMessages() {
try {
new ClassPathXmlApplicationContext(
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-invalidContext.xml");
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-invalidContext.xml")
.close();
fail("expected a parser error");
}
catch(BeanDefinitionStoreException e) {
@@ -293,16 +295,19 @@ public class InboundChannelAdapterParserTests {
public void imapWithSearchTermStrategy() {
AbstractMailReceiver receiver = this.getReceiver("imapWithSearch");
assertEquals(ImapMailReceiver.class, receiver.getClass());
Object sts = new DirectFieldAccessor(receiver).getPropertyValue("searchTermStrategy");
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object sts = receiverAccessor.getPropertyValue("searchTermStrategy");
assertNotNull(sts);
assertSame(context.getBean(SearchTermStrategy.class), sts);
assertEquals("flagged", receiverAccessor.getPropertyValue("userFlag"));
}
@Test
public void pop3WithSearchTermStrategy() {
try {
new ClassPathXmlApplicationContext(
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-pop3Search-context.xml");
"org/springframework/integration/mail/config/InboundChannelAdapterParserTests-pop3Search-context.xml")
.close();
fail("expected a parser error");
}
catch(BeanCreationException e) {

View File

@@ -271,7 +271,7 @@ As discussed in <<search-term>>, the default `SearchTermStrategy` will ignore me
Starting with _version 4.2.2_, the name of the user flag can be set using `setUserFlag` on the `MailReceiver` - this
allows multiple receivers to use a different flag (as long as the mail server supports user flags).
Changing the user flag is not currently supported by the XML namespace.
The attribute `user-flag` is available when configuring the adapter with the namespace.
[[mail-filtering]]
=== Email Message Filtering

View File

@@ -20,3 +20,9 @@ It was completely ignored; the gateway's reply goes to the next chain element, o
if the gateway is the last element.
This condition is now detected and disallowed.
If you have such configuration, simply remove the `reply-channel`.
==== Mail Changes
The customizable `userFlag` added in 4.2.2 to provide customization of the flag used to denote that the mail has been
seen is now available using the XML namespace.
See <<imap-seen>> for more information.