From e4ca8571e0e66e760c1a2705ca753d122131fa81 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 11 Sep 2018 16:58:07 -0400 Subject: [PATCH] File Source: do not deal with dir until start() StackOverflow: https://stackoverflow.com/questions/52273537/spring-boot-failed-to-load-applicationcontext --- .../file/FileReadingMessageSource.java | 24 +++---- .../file/AutoCreateDirectoryTests.java | 4 ++ .../AutoCreateDirectoryIntegrationTests.java | 19 +++--- src/reference/asciidoc/file.adoc | 64 ++++++++----------- src/reference/asciidoc/whats-new.adoc | 3 + 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java index 335d3fdef1..7522555e15 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java @@ -295,8 +295,19 @@ public class FileReadingMessageSource extends AbstractMessageSource @Override public void start() { - if (!this.running.getAndSet(true) && this.scanner instanceof Lifecycle) { - ((Lifecycle) this.scanner).start(); + if (!this.running.getAndSet(true)) { + if (!this.directory.exists() && this.autoCreateDirectory) { + this.directory.mkdirs(); + } + Assert.isTrue(this.directory.exists(), + "Source directory [" + this.directory + "] does not exist."); + Assert.isTrue(this.directory.isDirectory(), + "Source path [" + this.directory + "] does not point to a directory."); + Assert.isTrue(this.directory.canRead(), + "Source directory [" + this.directory + "] is not readable."); + if (this.scanner instanceof Lifecycle) { + ((Lifecycle) this.scanner).start(); + } } } @@ -315,15 +326,6 @@ public class FileReadingMessageSource extends AbstractMessageSource @Override protected void onInit() { Assert.notNull(this.directory, "'directory' must not be null"); - if (!this.directory.exists() && this.autoCreateDirectory) { - this.directory.mkdirs(); - } - Assert.isTrue(this.directory.exists(), - "Source directory [" + this.directory + "] does not exist."); - Assert.isTrue(this.directory.isDirectory(), - "Source path [" + this.directory + "] does not point to a directory."); - Assert.isTrue(this.directory.canRead(), - "Source directory [" + this.directory + "] is not readable."); Assert.state(!(this.scannerExplicitlySet && this.useWatchService), "The 'scanner' and 'useWatchService' options are mutually exclusive: " + this.scanner); diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/AutoCreateDirectoryTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/AutoCreateDirectoryTests.java index 29822d9996..3619cec826 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/AutoCreateDirectoryTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/AutoCreateDirectoryTests.java @@ -30,6 +30,8 @@ import org.springframework.beans.factory.BeanFactory; /** * @author Mark Fisher * @author Gary Russell + * @author Artem Bilan + * * @since 1.0.3 */ public class AutoCreateDirectoryTests { @@ -64,6 +66,7 @@ public class AutoCreateDirectoryTests { source.setDirectory(new File(INBOUND_PATH)); source.setBeanFactory(mock(BeanFactory.class)); source.afterPropertiesSet(); + source.start(); assertTrue(new File(INBOUND_PATH).exists()); } @@ -74,6 +77,7 @@ public class AutoCreateDirectoryTests { source.setAutoCreateDirectory(false); source.setBeanFactory(mock(BeanFactory.class)); source.afterPropertiesSet(); + source.start(); } @Test diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java index a7d9ac64d2..62ff792a25 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2018 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. @@ -36,13 +36,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher + * @author Artem Bilan */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class AutoCreateDirectoryIntegrationTests { private static final String BASE_PATH = - System.getProperty("java.io.tmpdir") + File.separator + AutoCreateDirectoryIntegrationTests.class.getSimpleName(); + System.getProperty("java.io.tmpdir") + File.separator + + AutoCreateDirectoryIntegrationTests.class.getSimpleName(); @Autowired @@ -64,18 +66,19 @@ public class AutoCreateDirectoryIntegrationTests { @Test - public void defaultInbound() throws Exception { + public void defaultInbound() { Object adapter = context.getBean("defaultInbound"); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); FileReadingMessageSource source = (FileReadingMessageSource) adapterAccessor.getPropertyValue("source"); assertEquals(Boolean.TRUE, new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory")); + source.start(); assertTrue(new File(BASE_PATH + File.separator + "defaultInbound").exists()); } @Test - public void customInbound() throws Exception { + public void customInbound() { Object adapter = context.getBean("customInbound"); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); FileReadingMessageSource source = (FileReadingMessageSource) @@ -86,7 +89,7 @@ public class AutoCreateDirectoryIntegrationTests { } @Test - public void defaultOutbound() throws Exception { + public void defaultOutbound() { Object adapter = context.getBean("defaultOutbound"); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); FileWritingMessageHandler handler = (FileWritingMessageHandler) @@ -97,7 +100,7 @@ public class AutoCreateDirectoryIntegrationTests { } @Test - public void customOutbound() throws Exception { + public void customOutbound() { Object adapter = context.getBean("customOutbound"); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); FileWritingMessageHandler handler = (FileWritingMessageHandler) @@ -108,7 +111,7 @@ public class AutoCreateDirectoryIntegrationTests { } @Test - public void defaultOutboundGateway() throws Exception { + public void defaultOutboundGateway() { Object gateway = context.getBean("defaultOutboundGateway"); DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway); FileWritingMessageHandler handler = (FileWritingMessageHandler) @@ -119,7 +122,7 @@ public class AutoCreateDirectoryIntegrationTests { } @Test - public void customOutboundGateway() throws Exception { + public void customOutboundGateway() { Object gateway = context.getBean("customOutboundGateway"); DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway); FileWritingMessageHandler handler = (FileWritingMessageHandler) diff --git a/src/reference/asciidoc/file.adoc b/src/reference/asciidoc/file.adoc index f08a977e1d..1fe8368a5b 100644 --- a/src/reference/asciidoc/file.adoc +++ b/src/reference/asciidoc/file.adoc @@ -37,7 +37,8 @@ Microsoft Windows, on the other hand, has a dedicated file attribute to indicate [IMPORTANT] ==== -Version 4.2 introduced the `IgnoreHiddenFileListFilter`. In prior versions, hidden files were included. +Version 4.2 introduced the `IgnoreHiddenFileListFilter`. +In prior versions, hidden files were included. With the default configuration, the `IgnoreHiddenFileListFilter` is triggered first, followed by the `AcceptOnceFileListFilter`. ==== @@ -53,8 +54,7 @@ This filter matches on the filename and modified time. Since version 4.0, this filter requires a `ConcurrentMetadataStore`. When used with a shared data store (such as `Redis` with the `RedisMetadataStore`), it lets filter keys be shared across multiple application instances or across a network file share being used by multiple servers. -Since version 4.1.5, this filter has a new property (`flushOnUpdate`), which causes it to flush the -metadata store on every update (if the store implements `Flushable`). +Since version 4.1.5, this filter has a new property (`flushOnUpdate`), which causes it to flush the metadata store on every update (if the store implements `Flushable`). ==== The following example configures a `FileReadingMessageSource` with a filter: @@ -97,7 +97,7 @@ The `CompositeFileListFilter` enables the composition, as the following example ---- ==== -If it is not possible to create the file with a temporary name and rename to the final name, Spring Integratio provides another alternative. +If it is not possible to create the file with a temporary name and rename to the final name, Spring Integration provides another alternative. Version 4.2 added the `LastModifiedFileListFilter`. This filter can be configured with an `age` property so that only files older than this value are passed by the filter. The age defaults to 60 seconds, but you should choose an age that is large enough to avoid picking up a file early (due to, say, network glitches). @@ -150,6 +150,9 @@ The `CompositeFileListFilter` also implements a `DiscardAwareFileListFilter` and NOTE: Since `CompositeFileListFilter` matches the files against all delegates, the `discardCallback` may be called several times for the same file. +Starting with version 5.1, the `FileReadingMessageSource` doesn't check a directory for existence and doesn't create it until its `start()` is called (typically via wrapping `SourcePollingChannelAdapter`). +Previously, there was no simple way to prevent an operation system permissions error when referencing the directory, for example from tests, or when permissions are applied later. + ==== Message Headers Starting with version 5.0, the `FileReadingMessageSource` (in addition to the `payload` as a polled `File`) populates the following headers to the outbound `Message`: @@ -157,7 +160,7 @@ Starting with version 5.0, the `FileReadingMessageSource` (in addition to the `p * `FileHeaders.FILENAME`: The `File.getName()` of the file to send. Can be used for subsequent rename or copy logic. * `FileHeaders.ORIGINAL_FILE`: The `File` object itself. -Typically, this header is populated automatically by framework components (such as <> or`<>) when we lose the original `File` object. +Typically, this header is populated automatically by framework components (such as <> or <>) when we lose the original `File` object. However, for consistency and convenience with any other custom use cases, this header can be useful to get access to the original file. * `FileHeaders.RELATIVE_PATH`: A new header introduced to represent the part of file path relative to the root directory for the scan. This header can be useful when the requirement is to restore a source directory hierarchy in the other places. @@ -167,21 +170,16 @@ For this purpose, the `DefaultFileNameGenerator` (see "`<` as a constructor argument. It is used by the internal (`PriorityBlockingQueue`) to reorder its content according to the business requirements. -Therefore, to process files in a specific order, you should provide a comparator to the `FileReadingMessageSource` -rather than ordering the list produced by a custom `DirectoryScanner`. +Therefore, to process files in a specific order, you should provide a comparator to the `FileReadingMessageSource` rather than ordering the list produced by a custom `DirectoryScanner`. Version 5.0 introduced `RecursiveDirectoryScanner` to perform file tree visiting. The implementation is based on the `Files.walk(Path start, int maxDepth, FileVisitOption... options)` functionality. @@ -248,8 +246,7 @@ Therefore, you can also leave off the `prevent-duplicates` and `ignore-hidden` a Spring Integration 4.2 introduced the `ignore-hidden` attribute. In prior versions, hidden files were included. ===== -The second channel adapter example uses a custom filter, the third uses the `filename-pattern` attribute to -add an `AntPathMatcher` based filter, and the fourth uses the `filename-regex` attribute to add a regular expression pattern-based filter to the `FileReadingMessageSource`. +The second channel adapter example uses a custom filter, the third uses the `filename-pattern` attribute to add an `AntPathMatcher` based filter, and the fourth uses the `filename-regex` attribute to add a regular expression pattern-based filter to the `FileReadingMessageSource`. The `filename-pattern` and `filename-regex` attributes are each mutually exclusive with the regular `filter` reference attribute. However, you can use the `filter` attribute to reference an instance of `CompositeFileListFilter` that combines any number of filters, including one or more pattern-based filters to fit your particular needs. @@ -301,7 +298,7 @@ You can inject a custom `DirectoryScanner` into the `> and <> as well as <> and <> for more -information about these facilities. +See <> and <> as well as <> and <> for more information about these facilities. When using Java configuration, an additional constructor is available, as the following example shows: diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 82932bc9c7..d86db9de0e 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -106,6 +106,9 @@ If you are using `FileExistsMode.APPEND` or `FileExistsMode.APPEND_NO_FLUSH` you This callback receives the newly created file and the message that triggered the callback. This could be used to write a CSV header, for an example. +The `FileReadingMessageSource` now doesn't check and create a directory until its `start()` is called. +So, if an Inbound Channel Adapter for the `FileReadingMessageSource` has `autoStartup = false`, there are no failures against the file system during application start up. + See <> for more information. [[x5.1-amqp]]