/* * Copyright 2002-2016 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.tail; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.tail.FileTailingMessageProducerSupport.FileTailingEvent; import org.springframework.integration.file.tail.FileTailingMessageProducerSupport.FileTailingIdleEvent; import org.springframework.messaging.Message; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @author Gary Russell * @author Gavin Gray * @author Artem Bilan * @author Ali Shahbour * * @since 3.0 */ public class FileTailingMessageProducerTests { private static final String TAIL_OPTIONS_FOLLOW_NAME_ALL_LINES = "-F -n +0"; @Rule public TailRule tailRule = new TailRule(TAIL_OPTIONS_FOLLOW_NAME_ALL_LINES); private final Log logger = LogFactory.getLog(this.getClass()); private final String tmpDir = System.getProperty("java.io.tmpdir"); private File testDir; private FileTailingMessageProducerSupport adapter; @Before public void setup() { File f = new File(tmpDir, "FileTailingMessageProducerTests"); f.mkdir(); this.testDir = f; } @After public void tearDown() { if (this.adapter != null) { adapter.stop(); } } @Test @TailAvailable public void testOS() throws Exception { OSDelegatingFileTailingMessageProducer adapter = new OSDelegatingFileTailingMessageProducer(); adapter.setOptions(TAIL_OPTIONS_FOLLOW_NAME_ALL_LINES); testGuts(adapter, "reader"); } @Test public void testApache() throws Exception { ApacheCommonsFileTailingMessageProducer adapter = new ApacheCommonsFileTailingMessageProducer(); adapter.setPollingDelay(100); adapter.setEnd(false); testGuts(adapter, "tailer"); } @Test @TailAvailable public void canRecalculateCommandWhenFileOrOptionsChanged() throws IOException { File firstFile = File.createTempFile("first", ".txt"); String firstOptions = "-f options"; File secondFile = File.createTempFile("second", ".txt"); String secondOptions = "-f newoptions"; OSDelegatingFileTailingMessageProducer adapter = new OSDelegatingFileTailingMessageProducer(); adapter.setFile(firstFile); adapter.setOptions(firstOptions); adapter.setOutputChannel(new QueueChannel()); adapter.setTailAttemptsDelay(500); adapter.setBeanFactory(mock(BeanFactory.class)); adapter.afterPropertiesSet(); adapter.start(); assertEquals("tail " + firstOptions + " " + firstFile.getAbsolutePath(), adapter.getCommand()); adapter.stop(); adapter.setFile(secondFile); adapter.start(); assertEquals("tail " + firstOptions + " " + secondFile.getAbsolutePath(), adapter.getCommand()); adapter.stop(); adapter.setOptions(secondOptions); adapter.start(); assertEquals("tail " + secondOptions + " " + secondFile.getAbsolutePath(), adapter.getCommand()); adapter.stop(); } @Test public void testIdleEvent() throws Exception { ApacheCommonsFileTailingMessageProducer adapter = new ApacheCommonsFileTailingMessageProducer(); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.afterPropertiesSet(); adapter.setTaskScheduler(taskScheduler); CountDownLatch idleCountDownLatch = new CountDownLatch(1); CountDownLatch fileExistCountDownLatch = new CountDownLatch(1); adapter.setApplicationEventPublisher(event -> { if (event instanceof FileTailingIdleEvent) { idleCountDownLatch.countDown(); } if (event instanceof FileTailingEvent) { FileTailingEvent fileTailingEvent = (FileTailingEvent) event; if (fileTailingEvent.getMessage().contains("File not found")) { fileExistCountDownLatch.countDown(); } } }); File file = spy(new File(this.testDir, "foo")); file.delete(); adapter.setFile(file); adapter.setOutputChannel(new NullChannel()); adapter.setIdleEventInterval(10); adapter.afterPropertiesSet(); adapter.start(); boolean noFile = fileExistCountDownLatch.await(10, TimeUnit.SECONDS); assertTrue("file does not exist event did not emit ", noFile); boolean noEvent = idleCountDownLatch.await(100, TimeUnit.MILLISECONDS); assertFalse("event should not emit when no file exit", noEvent); verify(file, atLeastOnce()).exists(); file.createNewFile(); boolean eventRaised = idleCountDownLatch.await(10, TimeUnit.SECONDS); assertTrue("idle event did not emit", eventRaised); adapter.stop(); file.delete(); } private void testGuts(FileTailingMessageProducerSupport adapter, String field) throws Exception { this.adapter = adapter; ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.afterPropertiesSet(); adapter.setTaskScheduler(taskScheduler); final List<FileTailingEvent> events = new ArrayList<FileTailingEvent>(); adapter.setApplicationEventPublisher(event -> { FileTailingEvent tailEvent = (FileTailingEvent) event; logger.debug(event); events.add(tailEvent); }); adapter.setFile(new File(testDir, "foo")); QueueChannel outputChannel = new QueueChannel(); adapter.setOutputChannel(outputChannel); adapter.setTailAttemptsDelay(500); adapter.setBeanFactory(mock(BeanFactory.class)); adapter.afterPropertiesSet(); File file = new File(testDir, "foo"); File renamed = new File(testDir, "bar"); file.delete(); renamed.delete(); adapter.start(); waitForField(adapter, field); FileOutputStream foo = new FileOutputStream(file); for (int i = 0; i < 50; i++) { foo.write(("hello" + i + "\n").getBytes()); } foo.flush(); foo.close(); for (int i = 0; i < 50; i++) { Message<?> message = outputChannel.receive(10000); assertNotNull("expected a non-null message", message); assertEquals("hello" + i, message.getPayload()); } file.renameTo(renamed); file = new File(testDir, "foo"); foo = new FileOutputStream(file); if (adapter instanceof ApacheCommonsFileTailingMessageProducer) { Thread.sleep(1000); } for (int i = 50; i < 100; i++) { foo.write(("hello" + i + "\n").getBytes()); } foo.flush(); foo.close(); for (int i = 50; i < 100; i++) { Message<?> message = outputChannel.receive(10000); assertNotNull("expected a non-null message", message); assertEquals("hello" + i, message.getPayload()); assertEquals(file, message.getHeaders().get(FileHeaders.ORIGINAL_FILE)); assertEquals(file.getName(), message.getHeaders().get(FileHeaders.FILENAME)); } assertThat(events.size(), greaterThanOrEqualTo(1)); } private void waitForField(FileTailingMessageProducerSupport adapter, String field) throws Exception { int n = 0; DirectFieldAccessor accessor = new DirectFieldAccessor(adapter); while (n < 100) { if (accessor.getPropertyValue(field) == null) { Thread.sleep(100); } else { return; } } fail("adapter failed to start"); } }