Skip to content

Commit e4ca857

Browse files
artembilangaryrussell
authored andcommitted
File Source: do not deal with dir until start()
StackOverflow: https://stackoverflow.com/questions/52273537/spring-boot-failed-to-load-applicationcontext
1 parent 90e4c54 commit e4ca857

File tree

5 files changed

+57
-57
lines changed

5 files changed

+57
-57
lines changed

spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,19 @@ public String getComponentType() {
295295

296296
@Override
297297
public void start() {
298-
if (!this.running.getAndSet(true) && this.scanner instanceof Lifecycle) {
299-
((Lifecycle) this.scanner).start();
298+
if (!this.running.getAndSet(true)) {
299+
if (!this.directory.exists() && this.autoCreateDirectory) {
300+
this.directory.mkdirs();
301+
}
302+
Assert.isTrue(this.directory.exists(),
303+
"Source directory [" + this.directory + "] does not exist.");
304+
Assert.isTrue(this.directory.isDirectory(),
305+
"Source path [" + this.directory + "] does not point to a directory.");
306+
Assert.isTrue(this.directory.canRead(),
307+
"Source directory [" + this.directory + "] is not readable.");
308+
if (this.scanner instanceof Lifecycle) {
309+
((Lifecycle) this.scanner).start();
310+
}
300311
}
301312
}
302313

@@ -315,15 +326,6 @@ public boolean isRunning() {
315326
@Override
316327
protected void onInit() {
317328
Assert.notNull(this.directory, "'directory' must not be null");
318-
if (!this.directory.exists() && this.autoCreateDirectory) {
319-
this.directory.mkdirs();
320-
}
321-
Assert.isTrue(this.directory.exists(),
322-
"Source directory [" + this.directory + "] does not exist.");
323-
Assert.isTrue(this.directory.isDirectory(),
324-
"Source path [" + this.directory + "] does not point to a directory.");
325-
Assert.isTrue(this.directory.canRead(),
326-
"Source directory [" + this.directory + "] is not readable.");
327329

328330
Assert.state(!(this.scannerExplicitlySet && this.useWatchService),
329331
"The 'scanner' and 'useWatchService' options are mutually exclusive: " + this.scanner);

spring-integration-file/src/test/java/org/springframework/integration/file/AutoCreateDirectoryTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
/**
3131
* @author Mark Fisher
3232
* @author Gary Russell
33+
* @author Artem Bilan
34+
*
3335
* @since 1.0.3
3436
*/
3537
public class AutoCreateDirectoryTests {
@@ -64,6 +66,7 @@ public void autoCreateForInboundEnabledByDefault() throws Exception {
6466
source.setDirectory(new File(INBOUND_PATH));
6567
source.setBeanFactory(mock(BeanFactory.class));
6668
source.afterPropertiesSet();
69+
source.start();
6770
assertTrue(new File(INBOUND_PATH).exists());
6871
}
6972

@@ -74,6 +77,7 @@ public void autoCreateForInboundDisabled() throws Exception {
7477
source.setAutoCreateDirectory(false);
7578
source.setBeanFactory(mock(BeanFactory.class));
7679
source.afterPropertiesSet();
80+
source.start();
7781
}
7882

7983
@Test

spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,13 +36,15 @@
3636

3737
/**
3838
* @author Mark Fisher
39+
* @author Artem Bilan
3940
*/
4041
@ContextConfiguration
4142
@RunWith(SpringJUnit4ClassRunner.class)
4243
public class AutoCreateDirectoryIntegrationTests {
4344

4445
private static final String BASE_PATH =
45-
System.getProperty("java.io.tmpdir") + File.separator + AutoCreateDirectoryIntegrationTests.class.getSimpleName();
46+
System.getProperty("java.io.tmpdir") + File.separator +
47+
AutoCreateDirectoryIntegrationTests.class.getSimpleName();
4648

4749

4850
@Autowired
@@ -64,18 +66,19 @@ public static void deleteBaseDirectory() {
6466

6567

6668
@Test
67-
public void defaultInbound() throws Exception {
69+
public void defaultInbound() {
6870
Object adapter = context.getBean("defaultInbound");
6971
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
7072
FileReadingMessageSource source = (FileReadingMessageSource)
7173
adapterAccessor.getPropertyValue("source");
7274
assertEquals(Boolean.TRUE,
7375
new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory"));
76+
source.start();
7477
assertTrue(new File(BASE_PATH + File.separator + "defaultInbound").exists());
7578
}
7679

7780
@Test
78-
public void customInbound() throws Exception {
81+
public void customInbound() {
7982
Object adapter = context.getBean("customInbound");
8083
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
8184
FileReadingMessageSource source = (FileReadingMessageSource)
@@ -86,7 +89,7 @@ public void customInbound() throws Exception {
8689
}
8790

8891
@Test
89-
public void defaultOutbound() throws Exception {
92+
public void defaultOutbound() {
9093
Object adapter = context.getBean("defaultOutbound");
9194
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
9295
FileWritingMessageHandler handler = (FileWritingMessageHandler)
@@ -97,7 +100,7 @@ public void defaultOutbound() throws Exception {
97100
}
98101

99102
@Test
100-
public void customOutbound() throws Exception {
103+
public void customOutbound() {
101104
Object adapter = context.getBean("customOutbound");
102105
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
103106
FileWritingMessageHandler handler = (FileWritingMessageHandler)
@@ -108,7 +111,7 @@ public void customOutbound() throws Exception {
108111
}
109112

110113
@Test
111-
public void defaultOutboundGateway() throws Exception {
114+
public void defaultOutboundGateway() {
112115
Object gateway = context.getBean("defaultOutboundGateway");
113116
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
114117
FileWritingMessageHandler handler = (FileWritingMessageHandler)
@@ -119,7 +122,7 @@ public void defaultOutboundGateway() throws Exception {
119122
}
120123

121124
@Test
122-
public void customOutboundGateway() throws Exception {
125+
public void customOutboundGateway() {
123126
Object gateway = context.getBean("customOutboundGateway");
124127
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
125128
FileWritingMessageHandler handler = (FileWritingMessageHandler)

src/reference/asciidoc/file.adoc

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Microsoft Windows, on the other hand, has a dedicated file attribute to indicate
3737

3838
[IMPORTANT]
3939
====
40-
Version 4.2 introduced the `IgnoreHiddenFileListFilter`. In prior versions, hidden files were included.
40+
Version 4.2 introduced the `IgnoreHiddenFileListFilter`.
41+
In prior versions, hidden files were included.
4142
With the default configuration, the `IgnoreHiddenFileListFilter` is triggered first, followed by the `AcceptOnceFileListFilter`.
4243
====
4344

@@ -53,8 +54,7 @@ This filter matches on the filename and modified time.
5354
Since version 4.0, this filter requires a `ConcurrentMetadataStore`.
5455
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.
5556
56-
Since version 4.1.5, this filter has a new property (`flushOnUpdate`), which causes it to flush the
57-
metadata store on every update (if the store implements `Flushable`).
57+
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`).
5858
====
5959

6060
The following example configures a `FileReadingMessageSource` with a filter:
@@ -97,7 +97,7 @@ The `CompositeFileListFilter` enables the composition, as the following example
9797
----
9898
====
9999

100-
If it is not possible to create the file with a temporary name and rename to the final name, Spring Integratio provides another alternative.
100+
If it is not possible to create the file with a temporary name and rename to the final name, Spring Integration provides another alternative.
101101
Version 4.2 added the `LastModifiedFileListFilter`.
102102
This filter can be configured with an `age` property so that only files older than this value are passed by the filter.
103103
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,14 +150,17 @@ The `CompositeFileListFilter` also implements a `DiscardAwareFileListFilter` and
150150

151151
NOTE: Since `CompositeFileListFilter` matches the files against all delegates, the `discardCallback` may be called several times for the same file.
152152

153+
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`).
154+
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.
155+
153156
==== Message Headers
154157

155158
Starting with version 5.0, the `FileReadingMessageSource` (in addition to the `payload` as a polled `File`) populates the following headers to the outbound `Message`:
156159

157160
* `FileHeaders.FILENAME`: The `File.getName()` of the file to send.
158161
Can be used for subsequent rename or copy logic.
159162
* `FileHeaders.ORIGINAL_FILE`: The `File` object itself.
160-
Typically, this header is populated automatically by framework components (such as <<file-splitter,splitters>> or`<<file-transforming,transformers>>) when we lose the original `File` object.
163+
Typically, this header is populated automatically by framework components (such as <<file-splitter,splitters>> or <<file-transforming,transformers>>) when we lose the original `File` object.
161164
However, for consistency and convenience with any other custom use cases, this header can be useful to get access to the original file.
162165
* `FileHeaders.RELATIVE_PATH`: A new header introduced to represent the part of file path relative to the root directory for the scan.
163166
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 "`<<file-writing-file-name
167170

168171
The `FileReadingMessageSource` does not produce messages for files from the directory immediately.
169172
It uses an internal queue for 'eligible files' returned by the `scanner`.
170-
The `scanEachPoll` option is used to ensure that the internal queue is refreshed with the latest input directory
171-
content on each poll.
172-
By default (`scanEachPoll = false`), the `FileReadingMessageSource` empties its queue before scanning the directory
173-
again.
173+
The `scanEachPoll` option is used to ensure that the internal queue is refreshed with the latest input directory content on each poll.
174+
By default (`scanEachPoll = false`), the `FileReadingMessageSource` empties its queue before scanning the directory again.
174175
This default behavior is particularly useful to reduce scans of large numbers of files in a directory.
175-
However, in cases where custom ordering is required, it is important to consider the effects of setting this flag to
176-
`true`.
176+
However, in cases where custom ordering is required, it is important to consider the effects of setting this flag to `true`.
177177
The order in which files are processed may not be as expected.
178178
By default, files in the queue are processed in their natural (`path`) order.
179-
New files added by a scan, even when the queue already has files, are inserted in the appropriate position to maintain
180-
that natural order.
179+
New files added by a scan, even when the queue already has files, are inserted in the appropriate position to maintain that natural order.
181180
To customize the order, the `FileReadingMessageSource` can accept a `Comparator<File>` as a constructor argument.
182181
It is used by the internal (`PriorityBlockingQueue`) to reorder its content according to the business requirements.
183-
Therefore, to process files in a specific order, you should provide a comparator to the `FileReadingMessageSource`
184-
rather than ordering the list produced by a custom `DirectoryScanner`.
182+
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`.
185183

186184
Version 5.0 introduced `RecursiveDirectoryScanner` to perform file tree visiting.
187185
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
248246
Spring Integration 4.2 introduced the `ignore-hidden` attribute. In prior versions, hidden files were included.
249247
=====
250248

251-
The second channel adapter example uses a custom filter, the third uses the `filename-pattern` attribute to
252-
add an `AntPathMatcher` based filter, and the fourth uses the `filename-regex` attribute to add a regular expression pattern-based filter to the `FileReadingMessageSource`.
249+
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`.
253250
The `filename-pattern` and `filename-regex` attributes are each mutually exclusive with the regular `filter` reference attribute.
254251
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.
255252

@@ -301,7 +298,7 @@ You can inject a custom `DirectoryScanner` into the `<int-file:inbound-channel-a
301298

302299
Doing so gives you full freedom to choose the ordering, listing, and locking strategies.
303300

304-
It is also important to understand that filters (including `patterns`, `regex`, `prevent-duplicates`, and otehrs) and `locker` instances are actually used by the `scanner`.
301+
It is also important to understand that filters (including `patterns`, `regex`, `prevent-duplicates`, and others) and `locker` instances are actually used by the `scanner`.
305302
Any of these attributes set on the adapter are subsequently injected into the internal `scanner`.
306303
For the case of an external `scanner`, all filter and locker attributes are prohibited on the `FileReadingMessageSource`.
307304
They must be specified (if required) on that custom `DirectoryScanner`.
@@ -324,14 +321,12 @@ If a new subdirectory is added, its creation event is used to walk the new subtr
324321
NOTE: There is an issue with `WatchKey` when its internal events `queue` is not drained by the program as quickly as the directory modification events occur.
325322
If the queue size is exceeded, a `StandardWatchEventKinds.OVERFLOW` is emitted to indicate that some file system events may be lost.
326323
In this case, the root directory is re-scanned completely.
327-
To avoid duplicates, consider using an appropriate `FileListFilter` (such as the `AcceptOnceFileListFilter`) or
328-
removing files when processing is complete.
324+
To avoid duplicates, consider using an appropriate `FileListFilter` (such as the `AcceptOnceFileListFilter`) or removing files when processing is complete.
329325

330326
The `WatchServiceDirectoryScanner` can be enabled through the `FileReadingMessageSource.use-watch-service` option, which is mutually exclusive with the `scanner` option.
331327
An internal `FileReadingMessageSource.WatchServiceDirectoryScanner` instance is populated for the provided `directory`.
332328

333-
In addition, now the `WatchService` polling logic can track the `StandardWatchEventKinds.ENTRY_MODIFY` and
334-
`StandardWatchEventKinds.ENTRY_DELETE`.
329+
In addition, now the `WatchService` polling logic can track the `StandardWatchEventKinds.ENTRY_MODIFY` and `StandardWatchEventKinds.ENTRY_DELETE`.
335330

336331
If you need to track the modification of existing files as well as new files, you should implement the `ENTRY_MODIFY` events logic in the `FileListFilter`.
337332
Otherwise, the files from those events are treated the same way.
@@ -345,6 +340,7 @@ For this purpose, the `watch-events` property (`FileReadingMessageSource.setWatc
345340
(`WatchEventType` is a public inner enumeration in `FileReadingMessageSource`.)
346341
With such an option, we can use one downstream flow logic for new files and use some other logic for modified files.
347342
The following example shows how to configure different logic for create and modify events in the same directory:
343+
348344
====
349345
[source,xml]
350346
----
@@ -372,13 +368,10 @@ Any other filters (including `prevent-duplicates="true"`) overwrote the filter u
372368
[NOTE]
373369
=====
374370
The use of a `HeadDirectoryScanner` is incompatible with an `AcceptOnceFileListFilter`.
375-
Since all filters are consulted during the poll decision, the `AcceptOnceFileListFilter` does not know
376-
that other filters might be temporarily filtering files.
377-
Even if files that were previously filtered by the `HeadDirectoryScanner.HeadFilter` are now available, the
378-
`AcceptOnceFileListFilter` filters them.
371+
Since all filters are consulted during the poll decision, the `AcceptOnceFileListFilter` does not know that other filters might be temporarily filtering files.
372+
Even if files that were previously filtered by the `HeadDirectoryScanner.HeadFilter` are now available, the `AcceptOnceFileListFilter` filters them.
379373
380-
Generally, instead of using an `AcceptOnceFileListFilter` in this case, you should remove the processed
381-
files so that the previously filtered files are available on a future poll.
374+
Generally, instead of using an `AcceptOnceFileListFilter` in this case, you should remove the processed files so that the previously filtered files are available on a future poll.
382375
=====
383376

384377
==== Configuring with Java Configuration
@@ -611,8 +604,7 @@ This class can deal with the following payload types:
611604

612605
For a String payload, you can configure the encoding and the charset.
613606

614-
To make things easier, you can configure the `FileWritingMessageHandler` as part of an outbound channel adapter or
615-
outbound gateway by using the XML namespace.
607+
To make things easier, you can configure the `FileWritingMessageHandler` as part of an outbound channel adapter or outbound gateway by using the XML namespace.
616608

617609
Starting with version 4.3, you can specify the buffer size to use when writing files.
618610

@@ -720,8 +712,7 @@ For other payloads, the `FileHeaders.SET_MODIFIED` (`file_setModified`) header i
720712
If the header is missing or has a value that is not a `Number`, the file is always replaced.
721713
`APPEND`::
722714
This mode lets you append message content to the existing file instead of creating a new file each time.
723-
Note that this attribute is mutually exclusive with the `temporary-file-suffix` attribute because, when it appends content to
724-
the existing file, the adapter no longer uses a temporary file.
715+
Note that this attribute is mutually exclusive with the `temporary-file-suffix` attribute because, when it appends content to the existing file, the adapter no longer uses a temporary file.
725716
The file is closed after each message.
726717
`APPEND_NO_FLUSH`::
727718
This option has the same semantics as `APPEND`, but the data is not flushed and the file is not closed after each message.
@@ -747,10 +738,8 @@ Spring Integration provides several flushing strategies to mitigate this data lo
747738
This is approximate and may be up to `1.33x` this time (with an average of `1.167x`).
748739
* Send a message containing a regular expression to the message handler's `trigger` method.
749740
Files with absolute path names matching the pattern are flushed.
750-
* Provide the handler with a custom `MessageFlushPredicate` implementation to modify the action taken when a message
751-
is sent to the `trigger` method.
752-
* Invoke one of the handler's `flushIfNeeded` methods by passing in a custom `FileWritingMessageHandler.FlushPredicate`
753-
or `FileWritingMessageHandler.MessageFlushPredicate` implementation.
741+
* Provide the handler with a custom `MessageFlushPredicate` implementation to modify the action taken when a message is sent to the `trigger` method.
742+
* Invoke one of the handler's `flushIfNeeded` methods by passing in a custom `FileWritingMessageHandler.FlushPredicate` or `FileWritingMessageHandler.MessageFlushPredicate` implementation.
754743

755744
The predicates are called for each open file.
756745
See the https://docs.spring.io/spring-integration/api/index.html[Javadoc] for these interfaces for more information.
@@ -1027,8 +1016,7 @@ The default is `true`.
10271016

10281017
The `FileSplitter` also splits any text-based `InputStream` into lines.
10291018
Starting with version 4.3, when used in conjunction with an FTP or SFTP streaming inbound channel adapter or an FTP or SFTP outbound gateway that uses the `stream` option to retrieve a file, the splitter automatically closes the session that supports the stream when the file is completely consumed
1030-
See <<ftp-streaming>> and <<sftp-streaming>> as well as <<ftp-outbound-gateway>> and <<sftp-outbound-gateway>> for more
1031-
information about these facilities.
1019+
See <<ftp-streaming>> and <<sftp-streaming>> as well as <<ftp-outbound-gateway>> and <<sftp-outbound-gateway>> for more information about these facilities.
10321020

10331021
When using Java configuration, an additional constructor is available, as the following example shows:
10341022

0 commit comments

Comments
 (0)