INT-1819 Mail pseudo-tx support

Added pseudo-tx support for Mail inbound adapters
For polling adapters no changes have been made other then returning a javax.mail.Message instead of its copy so
post-tx dispositions could be performed on it.
For Imap IDLE adapter changes are simiar to the once present in SPCA where TX synchronization logic was added to ImapIdleChannelAdapter
Couple of things to note:
First IDLE receives an array of messages while Polling task receives one message which means i need to sendMessage in the Polling task in the separate thread, so for maintaining single thread semantics we have now it uses single thread executor to send Messages
Renamed MSRH to TransactionalResourceHolder since we no longer use 'source' anywhere and in the case of IDLE there is no MessageSource. Its is truly a holder of attributes we want to make available for use (e.g., SpEL)

INT-1819 Polishing

- Change TransactionalResourceHolder to IntegrationResourceHolder
- Make messageSource available as an attribute
- Allow configuration of Executor for ImapIdle adapter
- Add parser test for TX ImapIdle adapter
- Fix bundlor config for mail
- Remove top level <transactional/> element that was added to core
- Restore 'legacy' mail attributes in TX, and add schema doc

INT-1819 Mail TX Reference Docs

Add reference documentation for mail transaction support.

INT-1819 Remove 'public abstract' from interface

Modifiers are not needed on an interface.
This commit is contained in:
Oleg Zhurakousky
2012-09-07 10:00:47 -04:00
parent e1dd8240d0
commit ee91a6ce5a
18 changed files with 487 additions and 128 deletions

View File

@@ -33,7 +33,6 @@ import javax.mail.internet.MimeMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -42,17 +41,18 @@ import org.springframework.util.Assert;
/**
* Base class for {@link MailReceiver} implementations.
*
*
* @author Arjen Poutsma
* @author Jonas Partner
* @author Mark Fisher
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public abstract class AbstractMailReceiver extends IntegrationObjectSupport implements MailReceiver, DisposableBean{
public final static String SI_USER_FLAG = "spring-integration-mail-adapter";
protected final Log logger = LogFactory.getLog(this.getClass());
private final URLName url;
@@ -68,7 +68,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
private volatile Folder folder;
private volatile boolean shouldDeleteMessages;
protected volatile int folderOpenMode = Folder.READ_ONLY;
private volatile Properties javaMailProperties = new Properties();
@@ -117,7 +117,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
/**
* Set the {@link Session}. Otherwise, the Session will be created by invocation of
* {@link Session#getInstance(Properties)} or {@link Session#getInstance(Properties, Authenticator)}.
*
*
* @see #setJavaMailProperties(Properties)
* @see #setJavaMailAuthenticator(Authenticator)
*/
@@ -129,7 +129,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
/**
* A new {@link Session} will be created with these properties (and the JavaMailAuthenticator if provided).
* Use either this method or {@link #setSession}, but not both.
*
*
* @see #setJavaMailAuthenticator(Authenticator)
* @see #setSession(Session)
*/
@@ -140,7 +140,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
/**
* Optional, sets the Authenticator to be used to obtain a session. This will not be used if
* {@link AbstractMailReceiver#setSession} has been used to configure the {@link Session} directly.
*
*
* @see #setSession(Session)
*/
public void setJavaMailAuthenticator(Authenticator javaMailAuthenticator) {
@@ -220,8 +220,8 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
}
this.folder.open(this.folderOpenMode);
}
public Message[] receive() throws javax.mail.MessagingException {
public Message[] receive() throws javax.mail.MessagingException {
synchronized (this.folderMonitor) {
try {
this.openFolder();
@@ -240,68 +240,89 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
if (messages.length > 0) {
this.fetchMessages(messages);
}
List<Message> copiedMessages = new LinkedList<Message>();
logger.debug("Recieved " + messages.length + " messages");
boolean recentFlagSupported = false;
Flags flags = this.getFolder().getPermanentFlags();
if (flags != null){
recentFlagSupported = flags.contains(Flags.Flag.RECENT);
if (logger.isDebugEnabled()) {
logger.debug("Received " + messages.length + " messages");
}
for (int i = 0; i < messages.length; i++) {
if (!recentFlagSupported){
if (flags != null && flags.contains(Flags.Flag.USER)){
if (logger.isDebugEnabled()){
logger.debug("USER flags are supported by this mail server. Flagging message with '" + SI_USER_FLAG + "' user flag");
}
Flags siFlags = new Flags();
siFlags.add(SI_USER_FLAG);
messages[i].setFlags(siFlags, true);
}
else {
if (logger.isDebugEnabled()){
logger.debug("USER flags are not supported by this mail server. Flagging message with system flag");
}
messages[i].setFlag(Flags.Flag.FLAGGED, true);
}
}
if (this.selectorExpression != null) {
Message message = messages[i];
if (this.selectorExpression.getValue(this.context, message, Boolean.class)){
this.setAdditionalFlags(message);
copiedMessages.add(new MimeMessage((MimeMessage) message));
}
else {
if (logger.isDebugEnabled()){
logger.debug("Fetched email with subject '" + message.getSubject() + "' will be discarded by the matching filter" +
" and will not be flagged as SEEN.");
}
}
}
else {
this.setAdditionalFlags(messages[i]);
copiedMessages.add(new MimeMessage((MimeMessage) messages[i]));
}
}
if (this.shouldDeleteMessages()) {
this.deleteMessages(messages);
}
return copiedMessages.toArray(new Message[copiedMessages.size()]);
Message[] filteredMessages = this.filterMessagesThruSelector(messages);
this.postProcessFilteredMessages(filteredMessages);
return filteredMessages;
}
finally {
MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
}
}
}
}
private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException {
this.setMessageFlags(filteredMessages);
if (this.shouldDeleteMessages()) {
this.deleteMessages(filteredMessages);
}
}
private void setMessageFlags(Message[] filteredMessages) throws MessagingException {
boolean recentFlagSupported = false;
Flags flags = this.getFolder().getPermanentFlags();
if (flags != null){
recentFlagSupported = flags.contains(Flags.Flag.RECENT);
}
for (Message message : filteredMessages) {
if (!recentFlagSupported){
if (flags != null && flags.contains(Flags.Flag.USER)){
if (logger.isDebugEnabled()){
logger.debug("USER flags are supported by this mail server. Flagging message with '" + SI_USER_FLAG + "' user flag");
}
Flags siFlags = new Flags();
siFlags.add(SI_USER_FLAG);
message.setFlags(siFlags, true);
}
else {
if (logger.isDebugEnabled()){
logger.debug("USER flags are not supported by this mail server. Flagging message with system flag");
}
message.setFlag(Flags.Flag.FLAGGED, true);
}
}
this.setAdditionalFlags(message);
}
}
/**
* Will filter Messages thru selector. Messages that did not pass selector filtering criteria
* will be filtered out and remain on the server as never touched.
*/
private Message[] filterMessagesThruSelector(Message[] messages) throws MessagingException {
List<Message> filteredMessages = new LinkedList<Message>();
for (int i = 0; i < messages.length; i++) {
MimeMessage message = (MimeMessage) messages[i];
if (this.selectorExpression != null) {
if (this.selectorExpression.getValue(this.context, message, Boolean.class)){
filteredMessages.add(message);
}
else {
if (logger.isDebugEnabled()){
logger.debug("Fetched email with subject '" + message.getSubject() + "' will be discarded by the matching filter" +
" and will not be flagged as SEEN.");
}
}
}
filteredMessages.add(message);
}
return filteredMessages.toArray(new Message[filteredMessages.size()]);
}
/**
* Fetches the specified messages from this receiver's folder. Default
* implementation {@link Folder#fetch(Message[], FetchProfile) fetches}
* every {@link javax.mail.FetchProfile.Item}.
*
*
* @param messages the messages to fetch
* @throws MessagingException in case of JavaMail errors
*/
@@ -315,7 +336,7 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
/**
* Deletes the given messages from this receiver's folder.
*
*
* @param messages the messages to delete
* @throws MessagingException in case of JavaMail errors
*/
@@ -326,9 +347,9 @@ public abstract class AbstractMailReceiver extends IntegrationObjectSupport impl
}
/**
* Optional method allowing you to set additional flags.
* Optional method allowing you to set additional flags.
* Currently only implemented in IMapMailReceiver.
*
*
* @param message
* @throws MessagingException
*/

View File

@@ -17,20 +17,29 @@
package org.springframework.integration.mail;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Store;
import javax.mail.internet.MimeMessage;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transaction.IntegrationResourceHolder;
import org.springframework.integration.transaction.TransactionSynchronizationFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* An event-driven Channel Adapter that receives mail messages from a mail
@@ -38,17 +47,24 @@ import org.springframework.util.Assert;
* messages will be converted and sent as Spring Integration Messages to the
* output channel. The Message payload will be the {@link javax.mail.Message}
* instance that was received.
*
*
* @author Arjen Poutsma
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public class ImapIdleChannelAdapter extends MessageProducerSupport {
public class ImapIdleChannelAdapter extends MessageProducerSupport implements BeanClassLoaderAware {
private final IdleTask idleTask = new IdleTask();
private volatile Executor sendingTaskExecutor = Executors.newFixedThreadPool(1);
private volatile boolean shouldReconnectAutomatically = true;
private volatile ClassLoader classLoader;
private volatile List<Advice> adviceChain;
private final ImapMailReceiver mailReceiver;
private volatile int reconnectDelay = 10000; // milliseconds
@@ -58,15 +74,35 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
private volatile ScheduledFuture<?> pingTask;
private volatile long connectionPingInterval = 10000;
private final ExceptionAwarePeriodicTrigger receivingTaskTrigger = new ExceptionAwarePeriodicTrigger();
private volatile TransactionSynchronizationFactory transactionSynchronizationFactory;
public ImapIdleChannelAdapter(ImapMailReceiver mailReceiver) {
Assert.notNull(mailReceiver, "'mailReceiver' must not be null");
this.mailReceiver = mailReceiver;
}
public void setTransactionSynchronizationFactory(
TransactionSynchronizationFactory transactionSynchronizationFactory) {
this.transactionSynchronizationFactory = transactionSynchronizationFactory;
}
public void setAdviceChain(List<Advice> adviceChain) {
this.adviceChain = adviceChain;
}
/**
* Specify an {@link Executor} used to send messages received by the
* adapter.
* @param sendingTaskExecutor the sendingTaskExecutor to set
*/
public void setSendingTaskExecutor(Executor sendingTaskExecutor) {
Assert.notNull(sendingTaskExecutor, "'sendingTaskExecutor' must not be null");
this.sendingTaskExecutor = sendingTaskExecutor;
}
/**
* Specify whether the IDLE task should reconnect automatically after
@@ -77,10 +113,14 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
this.shouldReconnectAutomatically = shouldReconnectAutomatically;
}
@Override
public String getComponentType() {
return "mail:imap-idle-channel-adapter";
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/*
* Lifecycle implementation
@@ -140,9 +180,11 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
if (logger.isDebugEnabled()) {
logger.debug("received " + mailMessages.length + " mail messages");
}
for (Message mailMessage : mailMessages) {
final MimeMessage copied = new MimeMessage((MimeMessage) mailMessage);
sendMessage(MessageBuilder.withPayload(copied).build());
for (final Message mailMessage : mailMessages) {
Runnable messageSendingTask = createMessageSendingTask(mailMessage);
sendingTaskExecutor.execute(messageSendingTask);
}
}
}
@@ -162,6 +204,39 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
}
}
private Runnable createMessageSendingTask(final Message mailMessage){
Runnable sendingTask = new Runnable() {
public void run() {
org.springframework.integration.Message<?> message =
MessageBuilder.withPayload(mailMessage).build();
if (TransactionSynchronizationManager.isActualTransactionActive()) {
IntegrationResourceHolder holder = new IntegrationResourceHolder();
holder.setMessage(message);
TransactionSynchronizationManager.bindResource(ImapIdleChannelAdapter.this, holder);
if (transactionSynchronizationFactory != null){
TransactionSynchronizationManager.
registerSynchronization(transactionSynchronizationFactory.create(ImapIdleChannelAdapter.this));
}
}
sendMessage(message);
}
};
// wrap in the TX proxy if neccessery
if (!CollectionUtils.isEmpty(adviceChain)) {
ProxyFactory proxyFactory = new ProxyFactory(sendingTask);
if (!CollectionUtils.isEmpty(adviceChain)) {
for (Advice advice : adviceChain) {
proxyFactory.addAdvice(advice);
}
}
sendingTask = (Runnable) proxyFactory.getProxy(classLoader);
}
return sendingTask;
}
private class PingTask implements Runnable {
@@ -176,9 +251,9 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
}
}
}
private class ExceptionAwarePeriodicTrigger implements Trigger {
private volatile boolean delayNextExecution;
@@ -186,15 +261,14 @@ public class ImapIdleChannelAdapter extends MessageProducerSupport {
if (delayNextExecution){
delayNextExecution = false;
return new Date(System.currentTimeMillis() + reconnectDelay);
}
}
else {
return new Date(System.currentTimeMillis());
}
}
}
public void delayNextExecution() {
this.delayNextExecution = true;
}
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.integration.mail.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -28,6 +26,8 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.mail.ImapIdleChannelAdapter;
import org.springframework.integration.mail.ImapMailReceiver;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;imap-idle-channel-adapter&gt; element in the 'mail' namespace.
@@ -46,7 +46,17 @@ public class ImapIdleChannelAdapterParser extends AbstractChannelAdapterParser {
builder.addPropertyReference("outputChannel", channelName);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel", "errorChannel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder.getBeanDefinition();
Element txElement = DomUtils.getChildElementByTagName(element, "transactional");
if (txElement != null){
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, txElement,
"synchronization-factory", "transactionSynchronizationFactory");
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "task-executor", "sendingTaskExecutor");
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(null,
DomUtils.getChildElementByTagName(element, "transactional"), beanDefinition, parserContext);
return beanDefinition;
}
private BeanDefinition parseImapMailReceiver(Element element, ParserContext parserContext) {

View File

@@ -108,6 +108,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="inboundMailAdapterType">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="error-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
@@ -140,6 +143,21 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="task-executor" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a bean that implements
org.springframework.core.task.TaskExecutor which is used
to send Messages received by this adapter.
If not provided, the adapter uses a single-threaded executor.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.core.task.TaskExecutor" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -230,14 +248,19 @@
<xsd:attribute name="should-delete-messages" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether mail messages should be deleted after retrieval.
Specify whether mail messages should be deleted after retrieval. Messages are deleted after
retrieval but before they are processed. If you wish to delete a message after completion
of message processing, use transaction synchronization instead.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="should-mark-messages-as-read" type="xsd:string" use="optional" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether mail messages should be marked as read aftre being retrieved (Not supported in POP3).
Specify whether mail messages should be marked as read after being retrieved (Not supported in POP3).
Messages are marked after
retrieval but before they are processed. If you wish to mark a message after completion
of message processing, use transaction synchronization instead.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.mail.config;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.mail.Folder;
import javax.mail.Message;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.mail.ImapIdleChannelAdapter;
import org.springframework.integration.mail.ImapMailReceiver;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Oleg Zhurakousky
*/
public class ImapIdelIntegrationTests {
@Test
//@Ignore
public void testWithTransactionSynchronization() throws Exception{
final AtomicBoolean block = new AtomicBoolean(false);
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("imap-idle-mock-integration-config.xml", this.getClass());
PostTransactionProcessor processor = context.getBean("syncProcessor", PostTransactionProcessor.class);
ImapIdleChannelAdapter adapter = context.getBean("customAdapter", ImapIdleChannelAdapter.class);
ImapMailReceiver receiver = TestUtils.getPropertyValue(adapter, "mailReceiver", ImapMailReceiver.class);
// setup mock scenario
receiver = spy(receiver);
doAnswer(new Answer<Object>() { // ensures that waitFornewMessages call blocks after a first execution
// to emulate the behavior of IDLE
public Object answer(InvocationOnMock invocation) throws Throwable {
if (block.get()){
Thread.sleep(5000);
}
block.set(true);
return null;
}
}).when(receiver).waitForNewMessages();
Message m1 = mock(Message.class);
doReturn(new Message[]{m1}).when(receiver).receive();
Folder folder = mock(Folder.class);
when(folder.isOpen()).thenReturn(true);
Field folderField = ReflectionUtils.findField(ImapMailReceiver.class, "folder");
folderField.setAccessible(true);
folderField.set(receiver, folder);
Field mrField = ImapIdleChannelAdapter.class.getDeclaredField("mailReceiver");
mrField.setAccessible(true);
mrField.set(adapter, receiver);
// end mock setup
adapter.start();
Thread.sleep(1000);
// validating that TXpost processor was invoked
adapter.stop();
context.destroy();
verify(processor, Mockito.times(1)).process(m1);
}
public static interface PostTransactionProcessor {
public void process(Message mailMessage);
}
}

View File

@@ -66,6 +66,21 @@
should-delete-messages="${mail.delete}"
search-term-strategy="searchTermStrategy"/>
<mail:imap-idle-channel-adapter id="transactionalAdapter"
store-uri="imap:foo"
channel="channel"
auto-startup="false"
should-delete-messages="true"
task-executor="executor">
<mail:transactional synchronization-factory="syncFactory" />
</mail:imap-idle-channel-adapter>
<integration:transaction-synchronization-factory id="syncFactory">
<integration:after-commit expression="'foo'" />
</integration:transaction-synchronization-factory>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager" />
<bean id="searchTermStrategy" class="org.springframework.integration.mail.config.ImapIdleChannelAdapterParserTests.TestSearchTermStrategy"/>
<util:properties id="javaMailProperties">

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.mail.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -29,7 +30,6 @@ 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;
@@ -78,6 +78,7 @@ public class ImapIdleChannelAdapterParserTests {
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldDeleteMessages"));
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead"));
assertNull(adapterAccessor.getPropertyValue("errorChannel"));
assertNull(adapterAccessor.getPropertyValue("adviceChain"));
}
@Test
public void simpleAdapterWithErrorChannel() {
@@ -161,6 +162,27 @@ public class ImapIdleChannelAdapterParserTests {
assertSame(autoChannel, TestUtils.getPropertyValue(autoChannelAdapter, "outputChannel"));
}
@Test
public void transactionalAdapter() {
Object adapter = context.getBean("transactionalAdapter");
assertEquals(ImapIdleChannelAdapter.class, adapter.getClass());
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
Object channel = context.getBean("channel");
assertSame(channel, adapterAccessor.getPropertyValue("outputChannel"));
assertEquals(Boolean.FALSE, adapterAccessor.getPropertyValue("autoStartup"));
Object receiver = adapterAccessor.getPropertyValue("mailReceiver");
assertEquals(ImapMailReceiver.class, receiver.getClass());
DirectFieldAccessor receiverAccessor = new DirectFieldAccessor(receiver);
Object url = receiverAccessor.getPropertyValue("url");
assertEquals(new URLName("imap:foo"), url);
Properties properties = (Properties) receiverAccessor.getPropertyValue("javaMailProperties");
assertEquals(0, properties.size());
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldDeleteMessages"));
assertEquals(Boolean.TRUE, receiverAccessor.getPropertyValue("shouldMarkMessagesAsRead"));
assertNull(adapterAccessor.getPropertyValue("errorChannel"));
assertEquals(context.getBean("executor"), adapterAccessor.getPropertyValue("sendingTaskExecutor"));
assertNotNull(adapterAccessor.getPropertyValue("adviceChain"));
}
public static class TestSearchTermStrategy implements SearchTermStrategy {
public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.2.xsd
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail-2.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:util="http://www.springframework.org/schema/util">
<int-mail:imap-idle-channel-adapter id="customAdapter"
store-uri="imaps://foo.com:password@imap.foo.com/INBOX"
channel="nullChannel"
auto-startup="false"
should-delete-messages="false">
<int-mail:transactional synchronization-factory="syncFactory"/>
</int-mail:imap-idle-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:before-commit expression="@syncProcessor.process(payload)"/>
</int:transaction-synchronization-factory>
<bean id="syncProcessor" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.mail.config.ImapIdelIntegrationTests.PostTransactionProcessor"/>
</bean>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>