1
1
/*
2
- * Copyright 2002-2022 the original author or authors.
2
+ * Copyright 2002-2023 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
20
20
import java .io .IOException ;
21
21
import java .nio .file .FileSystems ;
22
22
import java .nio .file .FileVisitResult ;
23
+ import java .nio .file .FileVisitor ;
23
24
import java .nio .file .Files ;
24
25
import java .nio .file .Path ;
25
26
import java .nio .file .SimpleFileVisitor ;
29
30
import java .nio .file .WatchService ;
30
31
import java .nio .file .attribute .BasicFileAttributes ;
31
32
import java .util .Arrays ;
33
+ import java .util .Collections ;
32
34
import java .util .Comparator ;
33
35
import java .util .Iterator ;
34
36
import java .util .LinkedHashSet ;
39
41
import java .util .concurrent .ConcurrentMap ;
40
42
import java .util .concurrent .PriorityBlockingQueue ;
41
43
import java .util .concurrent .atomic .AtomicBoolean ;
44
+ import java .util .function .Predicate ;
42
45
import java .util .regex .Matcher ;
43
46
44
47
import org .springframework .context .Lifecycle ;
@@ -122,6 +125,10 @@ public class FileReadingMessageSource extends AbstractMessageSource<File> implem
122
125
123
126
private WatchEventType [] watchEvents = {WatchEventType .CREATE };
124
127
128
+ private int watchMaxDepth = Integer .MAX_VALUE ;
129
+
130
+ private Predicate <Path > watchDirPredicate = path -> true ;
131
+
125
132
/**
126
133
* Create a FileReadingMessageSource with a naturally ordered queue of unbounded capacity.
127
134
*/
@@ -237,15 +244,14 @@ public void setLocker(FileLocker locker) {
237
244
* Set this flag if you want to make sure the internal queue is
238
245
* refreshed with the latest content of the input directory on each poll.
239
246
* <p>
240
- * By default this implementation will empty its queue before looking at the
247
+ * By default, this implementation will empty its queue before looking at the
241
248
* directory again. In cases where order is relevant it is important to
242
249
* consider the effects of setting this flag. The internal
243
250
* {@link java.util.concurrent.BlockingQueue} that this class is keeping
244
251
* will more likely be out of sync with the file system if this flag is set
245
252
* to false, but it will change more often (causing expensive reordering) if it is set to true.
246
- * @param scanEachPoll
247
- * whether or not the component should re-scan (as opposed to not
248
- * rescanning until the entire backlog has been delivered)
253
+ * @param scanEachPoll whether the component should re-scan (as opposed to not
254
+ * rescanning until the entire backlog has been delivered)
249
255
*/
250
256
public void setScanEachPoll (boolean scanEachPoll ) {
251
257
this .scanEachPoll = scanEachPoll ;
@@ -282,6 +288,28 @@ public void setWatchEvents(WatchEventType... watchEvents) {
282
288
this .watchEvents = Arrays .copyOf (watchEvents , watchEvents .length );
283
289
}
284
290
291
+ /**
292
+ * Set a max depth for the {@link Files#walkFileTree(Path, Set, int, FileVisitor)} API when
293
+ * {@link #useWatchService} is enabled.
294
+ * Defaults to {@link Integer#MAX_VALUE} - walk the whole tree.
295
+ * @param watchMaxDepth the depth for {@link Files#walkFileTree(Path, Set, int, FileVisitor)}.
296
+ * @since 6.1
297
+ */
298
+ public void setWatchMaxDepth (int watchMaxDepth ) {
299
+ this .watchMaxDepth = watchMaxDepth ;
300
+ }
301
+
302
+ /**
303
+ * Set a {@link Predicate} to check a directory in the {@link Files#walkFileTree(Path, Set, int, FileVisitor)} call
304
+ * if it is eligible for {@link WatchService}.
305
+ * @param watchDirPredicate the {@link Predicate} to check dirs for walking.
306
+ * @since 6.1
307
+ */
308
+ public void setWatchDirPredicate (Predicate <Path > watchDirPredicate ) {
309
+ Assert .notNull (watchDirPredicate , "'watchDirPredicate' must not be null." );
310
+ this .watchDirPredicate = watchDirPredicate ;
311
+ }
312
+
285
313
@ Override
286
314
public String getComponentType () {
287
315
return "file:inbound-channel-adapter" ;
@@ -299,16 +327,16 @@ public void start() {
299
327
() -> "Source path [" + this .directory + "] does not point to a directory." );
300
328
Assert .isTrue (this .directory .canRead (),
301
329
() -> "Source directory [" + this .directory + "] is not readable." );
302
- if (this .scanner instanceof Lifecycle ) {
303
- (( Lifecycle ) this . scanner ) .start ();
330
+ if (this .scanner instanceof Lifecycle lifecycle ) {
331
+ lifecycle .start ();
304
332
}
305
333
}
306
334
}
307
335
308
336
@ Override
309
337
public void stop () {
310
- if (this .running .getAndSet (false ) && this .scanner instanceof Lifecycle ) {
311
- (( Lifecycle ) this . scanner ) .stop ();
338
+ if (this .running .getAndSet (false ) && this .scanner instanceof Lifecycle lifecycle ) {
339
+ lifecycle .stop ();
312
340
}
313
341
}
314
342
@@ -418,8 +446,8 @@ private class WatchServiceDirectoryScanner extends DefaultDirectoryScanner imple
418
446
419
447
@ Override
420
448
public void setFilter (FileListFilter <File > filter ) {
421
- if (filter instanceof DiscardAwareFileListFilter ) {
422
- (( DiscardAwareFileListFilter < File >) filter ) .addDiscardCallback (this .filesToPoll ::add );
449
+ if (filter instanceof DiscardAwareFileListFilter < File > discardAwareFileListFilter ) {
450
+ discardAwareFileListFilter .addDiscardCallback (this .filesToPoll ::add );
423
451
}
424
452
super .setFilter (filter );
425
453
}
@@ -505,8 +533,8 @@ private void processFilesFromNormalEvent(Set<File> files, File parentDir, WatchE
505
533
logger .debug (() -> "Watch event [" + event .kind () + "] for file [" + file + "]" );
506
534
507
535
if (StandardWatchEventKinds .ENTRY_DELETE .equals (event .kind ())) {
508
- if (getFilter () instanceof ResettableFileListFilter ) {
509
- (( ResettableFileListFilter < File >) getFilter ()) .remove (file );
536
+ if (getFilter () instanceof ResettableFileListFilter < File > resettableFileListFilter ) {
537
+ resettableFileListFilter .remove (file );
510
538
}
511
539
boolean fileRemoved = files .remove (file );
512
540
if (fileRemoved ) {
@@ -540,8 +568,8 @@ private void processFilesFromOverflowEvent(Set<File> files, WatchEvent<?> event)
540
568
}
541
569
this .pathKeys .clear ();
542
570
543
- if (event .context () != null && event .context () instanceof Path ) {
544
- files .addAll (walkDirectory (( Path ) event . context () , event .kind ()));
571
+ if (event .context () != null && event .context () instanceof Path path ) {
572
+ files .addAll (walkDirectory (path , event .kind ()));
545
573
}
546
574
else {
547
575
files .addAll (walkDirectory (FileReadingMessageSource .this .directory .toPath (), event .kind ()));
@@ -552,25 +580,32 @@ private Set<File> walkDirectory(Path directory, final WatchEvent.Kind<?> kind) {
552
580
final Set <File > walkedFiles = new LinkedHashSet <>();
553
581
try {
554
582
registerWatch (directory );
555
- Files .walkFileTree (directory , new SimpleFileVisitor <Path >() {
556
-
557
- @ Override
558
- public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs ) throws IOException {
559
- FileVisitResult fileVisitResult = super .preVisitDirectory (dir , attrs );
560
- registerWatch (dir );
561
- return fileVisitResult ;
562
- }
563
-
564
- @ Override
565
- public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
566
- FileVisitResult fileVisitResult = super .visitFile (file , attrs );
567
- if (!StandardWatchEventKinds .ENTRY_MODIFY .equals (kind )) {
568
- walkedFiles .add (file .toFile ());
569
- }
570
- return fileVisitResult ;
571
- }
572
-
573
- });
583
+ Files .walkFileTree (directory , Collections .emptySet (), FileReadingMessageSource .this .watchMaxDepth ,
584
+ new SimpleFileVisitor <>() {
585
+
586
+ @ Override
587
+ public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs )
588
+ throws IOException {
589
+
590
+ if (FileReadingMessageSource .this .watchDirPredicate .test (dir )) {
591
+ registerWatch (dir );
592
+ return FileVisitResult .CONTINUE ;
593
+ }
594
+ else {
595
+ return FileVisitResult .SKIP_SUBTREE ;
596
+ }
597
+ }
598
+
599
+ @ Override
600
+ public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
601
+ FileVisitResult fileVisitResult = super .visitFile (file , attrs );
602
+ if (!StandardWatchEventKinds .ENTRY_MODIFY .equals (kind )) {
603
+ walkedFiles .add (file .toFile ());
604
+ }
605
+ return fileVisitResult ;
606
+ }
607
+
608
+ });
574
609
}
575
610
catch (IOException ex ) {
576
611
logger .error (ex , () -> "Failed to walk directory: " + directory .toString ());
0 commit comments