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

@@ -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>