/* * Copyright 2016-2017 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.file.dsl; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.Lifecycle; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.annotation.IntegrationComponentScan; import org.springframework.integration.annotation.MessagingGateway; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlowDefinition; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.dsl.Pollers; import org.springframework.integration.dsl.StandardIntegrationFlow; import org.springframework.integration.dsl.channel.MessageChannels; import org.springframework.integration.file.DefaultFileNameGenerator; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.FileReadingMessageSource; import org.springframework.integration.file.splitter.FileSplitter; import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.file.tail.ApacheCommonsFileTailingMessageProducer; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; import org.springframework.stereotype.Service; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.StreamUtils; /** * @author Artem Bilan * * @since 5.0 */ @RunWith(SpringRunner.class) @DirtiesContext public class FileTests { @ClassRule public static final TemporaryFolder tmpDir = new TemporaryFolder(); @Autowired private ListableBeanFactory beanFactory; @Autowired private ControlBusGateway controlBus; @Autowired @Qualifier("fileFlow1Input") private MessageChannel fileFlow1Input; @Autowired @Qualifier("fileWriting.handler") private MessageHandler fileWritingMessageHandler; @Autowired @Qualifier("tailChannel") private PollableChannel tailChannel; @Autowired private ApacheCommonsFileTailingMessageProducer tailer; @Autowired @Qualifier("fileReadingResultChannel") private PollableChannel fileReadingResultChannel; @Autowired @Qualifier("fileWritingInput") private MessageChannel fileWritingInput; @Autowired @Qualifier("fileWritingResultChannel") private PollableChannel fileWritingResultChannel; @Autowired @Qualifier("fileSplittingResultChannel") private PollableChannel fileSplittingResultChannel; @Autowired @Qualifier("fileTriggerFlow.input") private MessageChannel fileTriggerFlowInput; @Autowired private CountDownLatch flushPredicateCalled; @Test public void testFileHandler() throws Exception { Message<?> message = MessageBuilder.withPayload("foo").setHeader(FileHeaders.FILENAME, "foo").build(); try { this.fileFlow1Input.send(message); fail("NullPointerException expected"); } catch (Exception e) { assertThat(e, instanceOf(MessageHandlingException.class)); assertThat(e.getCause(), instanceOf(NullPointerException.class)); } DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); fileNameGenerator.setBeanFactory(this.beanFactory); Object targetFileWritingMessageHandler = this.fileWritingMessageHandler; if (this.fileWritingMessageHandler instanceof Advised) { TargetSource targetSource = ((Advised) this.fileWritingMessageHandler).getTargetSource(); if (targetSource != null) { targetFileWritingMessageHandler = targetSource.getTarget(); } } DirectFieldAccessor dfa = new DirectFieldAccessor(targetFileWritingMessageHandler); assertEquals(Boolean.FALSE, dfa.getPropertyValue("flushWhenIdle")); assertEquals(60000L, dfa.getPropertyValue("flushInterval")); dfa.setPropertyValue("fileNameGenerator", fileNameGenerator); this.fileFlow1Input.send(message); assertTrue(new File(tmpDir.getRoot(), "foo").exists()); this.fileTriggerFlowInput.send(new GenericMessage<>("trigger")); assertTrue(this.flushPredicateCalled.await(10, TimeUnit.SECONDS)); } @Test public void testMessageProducerFlow() throws Exception { FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), "TailTest")); for (int i = 0; i < 50; i++) { file.write((i + "\n").getBytes()); } this.tailer.start(); for (int i = 0; i < 50; i++) { Message<?> message = this.tailChannel.receive(5000); assertNotNull(message); assertEquals("hello " + i, message.getPayload()); } assertNull(this.tailChannel.receive(1)); this.controlBus.send("@tailer.stop()"); file.close(); } @Autowired private PollableChannel filePollingErrorChannel; @Test public void testFileReadingFlow() throws Exception { List<Integer> evens = new ArrayList<>(25); for (int i = 0; i < 50; i++) { boolean even = i % 2 == 0; String extension = even ? ".sitest" : ".foofile"; if (even) { evens.add(i); } FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), i + extension)); file.write(("" + i).getBytes()); file.flush(); file.close(); } Message<?> message = fileReadingResultChannel.receive(60000); assertNotNull(message); Object payload = message.getPayload(); assertThat(payload, instanceOf(List.class)); @SuppressWarnings("unchecked") List<String> result = (List<String>) payload; assertEquals(25, result.size()); result.forEach(s -> assertTrue(evens.contains(Integer.parseInt(s)))); new File(tmpDir.getRoot(), "a.sitest").createNewFile(); Message<?> receive = this.filePollingErrorChannel.receive(60000); assertNotNull(receive); assertThat(receive, instanceOf(ErrorMessage.class)); } @Test public void testFileWritingFlow() throws Exception { String payload = "Spring Integration"; this.fileWritingInput.send(new GenericMessage<>(payload)); Message<?> receive = this.fileWritingResultChannel.receive(1000); assertNotNull(receive); assertThat(receive.getPayload(), instanceOf(File.class)); File resultFile = (File) receive.getPayload(); assertThat(resultFile.getAbsolutePath(), endsWith(TestUtils.applySystemFileSeparator("fileWritingFlow/foo.write"))); String fileContent = StreamUtils.copyToString(new FileInputStream(resultFile), Charset.defaultCharset()); assertEquals(payload, fileContent); } @Autowired @Qualifier("fileSplitter.handler") private MessageHandler fileSplitter; @Test public void testFileSplitterFlow() throws Exception { FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), "foo.tmp")); file.write(("HelloWorld\näöüß").getBytes(Charset.defaultCharset())); file.flush(); file.close(); Message<?> receive = this.fileSplittingResultChannel.receive(10000); assertNotNull(receive); assertThat(receive.getPayload(), instanceOf(FileSplitter.FileMarker.class)); // FileMarker.Mark.START assertEquals(0, receive.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE)); receive = this.fileSplittingResultChannel.receive(10000); assertNotNull(receive); //HelloWorld receive = this.fileSplittingResultChannel.receive(10000); assertNotNull(receive); //äöüß receive = this.fileSplittingResultChannel.receive(10000); assertNotNull(receive); assertThat(receive.getPayload(), instanceOf(FileSplitter.FileMarker.class)); // FileMarker.Mark.END assertNull(this.fileSplittingResultChannel.receive(1)); assertEquals(StandardCharsets.US_ASCII, TestUtils.getPropertyValue(this.fileSplitter, "charset")); } @Autowired @Qualifier("dynamicAdaptersResult") PollableChannel dynamicAdaptersResult; @Autowired private MyService myService; @Test public void testDynamicFileFlows() throws Exception { File newFolder1 = tmpDir.newFolder(); FileOutputStream file = new FileOutputStream(new File(newFolder1, "foo")); file.write(("foo").getBytes()); file.flush(); file.close(); File newFolder2 = tmpDir.newFolder(); file = new FileOutputStream(new File(newFolder2, "bar")); file.write(("bar").getBytes()); file.flush(); file.close(); this.myService.pollDirectories(newFolder1, newFolder2); Set<String> payloads = new TreeSet<>(); Message<?> receive = this.dynamicAdaptersResult.receive(10000); assertNotNull(receive); payloads.add((String) receive.getPayload()); receive = this.dynamicAdaptersResult.receive(10000); assertNotNull(receive); payloads.add((String) receive.getPayload()); assertArrayEquals(new String[] { "bar", "foo" }, payloads.toArray()); } @MessagingGateway(defaultRequestChannel = "controlBus.input") private interface ControlBusGateway { void send(String command); } @Configuration @EnableIntegration @ComponentScan @IntegrationComponentScan public static class ContextConfiguration { @Bean public IntegrationFlow controlBus() { return IntegrationFlowDefinition::controlBus; } @Bean public IntegrationFlow fileTriggerFlow() { return f -> f.handle("fileWriting.handler", "trigger"); } @Bean public CountDownLatch flushPredicateCalled() { return new CountDownLatch(1); } @Bean public IntegrationFlow fileFlow1() { return IntegrationFlows.from("fileFlow1Input") .handle(Files.outboundAdapter(tmpDir.getRoot()) .fileNameGenerator(message -> null) .fileExistsMode(FileExistsMode.APPEND_NO_FLUSH) .flushInterval(60000) .flushWhenIdle(false) .flushPredicate((fileAbsolutePath, firstWrite, lastWrite, filterMessage) -> { flushPredicateCalled().countDown(); return true; }), c -> c.id("fileWriting")) .get(); } @Bean public IntegrationFlow tailFlow() { return IntegrationFlows .from(Files.tailAdapter(new File(tmpDir.getRoot(), "TailTest")) .delay(500) .end(false) .id("tailer") .autoStartup(false)) .transform("hello "::concat) .channel(MessageChannels.queue("tailChannel")) .get(); } @Bean public IntegrationFlow fileReadingFlow() { return IntegrationFlows .from(Files.inboundAdapter(tmpDir.getRoot()) .patternFilter("*.sitest") .useWatchService(true) .watchEvents(FileReadingMessageSource.WatchEventType.CREATE, FileReadingMessageSource.WatchEventType.MODIFY), e -> e.poller(Pollers.fixedDelay(100) .errorChannel("filePollingErrorChannel"))) .filter(File.class, p -> !p.getName().startsWith("a"), e -> e.throwExceptionOnRejection(true)) .transform(Files.toStringTransformer()) .aggregate(a -> a.correlationExpression("1") .releaseStrategy(g -> g.size() == 25)) .channel(MessageChannels.queue("fileReadingResultChannel")) .get(); } @Bean public PollableChannel filePollingErrorChannel() { return new QueueChannel(); } @Bean public IntegrationFlow fileWritingFlow() { return IntegrationFlows.from("fileWritingInput") .enrichHeaders(h -> h.header(FileHeaders.FILENAME, "foo.write") .header("directory", new File(tmpDir.getRoot(), "fileWritingFlow"))) .handle(Files.outboundGateway(m -> m.getHeaders().get("directory"))) .channel(MessageChannels.queue("fileWritingResultChannel")) .get(); } @Bean public IntegrationFlow fileSplitterFlow() { return IntegrationFlows .from(Files.inboundAdapter(tmpDir.getRoot()) .filterFunction(f -> "foo.tmp".equals(f.getName())), e -> e.poller(p -> p.fixedDelay(100))) .split(Files.splitter() .markers() .charset(StandardCharsets.US_ASCII) .applySequence(true), e -> e.id("fileSplitter")) .channel(c -> c.queue("fileSplittingResultChannel")) .get(); } @Bean public PollableChannel dynamicAdaptersResult() { return new QueueChannel(); } } @Service public static class MyService { @Autowired private AutowireCapableBeanFactory beanFactory; @Autowired @Qualifier("dynamicAdaptersResult") PollableChannel dynamicAdaptersResult; void pollDirectories(File... directories) { for (File directory : directories) { StandardIntegrationFlow integrationFlow = IntegrationFlows .from(Files.inboundAdapter(directory), e -> e.poller(p -> p.fixedDelay(1000)) .id(directory.getName() + ".adapter")) .transform(Files.toStringTransformer(), e -> e.id(directory.getName() + ".transformer")) .channel(this.dynamicAdaptersResult) .get(); this.beanFactory.initializeBean(integrationFlow, directory.getName()); this.beanFactory.getBean(directory.getName() + ".transformer", Lifecycle.class).start(); this.beanFactory.getBean(directory.getName() + ".adapter", Lifecycle.class).start(); } } } }