Skip to content

Commit c30df66

Browse files
artembilancppwfs
authored andcommitted
spring-projectsGH-10083: Apply Nullability to file module
Related to: spring-projects#10083 * The change in the `file.dsl` package has affected the `ComponentsRegistration` contract in the core module. And that one has led to respective fixes in the `AMQP`, `JPA`, `Kafka` modules for DSL packages * Fix `SimpleJsonSerializer` contract to return an empty JSON instead of `null`. Something similar to what Jackson 3 provides for us by default. * Rework a `WatchServiceDirectoryScanner` logic to initialize its internals in the ctor to avoid null-away errors * Fix `FileWritingMessageHandler`, `HeadDirectoryScanner`, and `RecursiveDirectoryScanner` code style and logic according to IDE suggestions and new Nullability functionality * Fix classes in the `file.config` package according to new Nullability logic * Implement Nullability for `FileListFilter` abstraction and its implementations * Fix `FileChannelCache` for a proper constant name. * Respectively, fix the `NioFileLockerTests` to use a new `FileChannelCache.CHANNEL_CACHE` constant * The remote directory could be null for some protocol operations. Therefore implement that respectively in the `FileInfo`, `Session` and `RemoteFileOperations` The change might look big, but it is inevitable and pretty straightforward * Remove redundant FQCN for `@Nullable` on the `FileReadingMessageSource.doReceive()`. That was a first JSpecify annotation changed in this class without replacing existing import for deprecated one from SF * Fix `FileTransferringMessageHandler.handleMessageInternal()` logic to fail when file could not be transferred to remote dir. Previously it returned silently. The error was only logged in the `RemoteFileTemplate` * Fix language in `AbstractInboundFileSynchronizingMessageSource.doReceive()` JavaDoc * Bring back a proper JavaDoc for `RemoteFileOperations.rename()` * Extract pattern variable for the `if` in the `FileWritingMessageHandler.handleRequestMessage()` * Fix indents in the `HeadDirectoryScanner.HeadFilter` record
1 parent dfed795 commit c30df66

File tree

110 files changed

+856
-717
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+856
-717
lines changed

spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundChannelAdapterSpec.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Collections;
2020
import java.util.Map;
2121

22+
import org.jspecify.annotations.Nullable;
23+
2224
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
2325
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
2426
import org.springframework.integration.dsl.ComponentsRegistration;
@@ -48,8 +50,9 @@ protected AmqpInboundChannelAdapterSpec(MessageListenerContainerSpec<?, C> liste
4850
}
4951

5052
@Override
51-
public Map<Object, String> getComponentsToRegister() {
52-
return Collections.singletonMap(this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId());
53+
public Map<Object, @Nullable String> getComponentsToRegister() {
54+
return Collections.<Object, @Nullable String>singletonMap(
55+
this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId());
5356
}
5457

5558
}

spring-integration-amqp/src/main/java/org/springframework/integration/amqp/dsl/AmqpInboundGatewaySpec.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Collections;
2020
import java.util.Map;
2121

22+
import org.jspecify.annotations.Nullable;
23+
2224
import org.springframework.amqp.core.AmqpTemplate;
2325
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
2426
import org.springframework.integration.amqp.inbound.AmqpInboundGateway;
@@ -61,8 +63,9 @@ protected AmqpInboundGatewaySpec(AbstractMessageListenerContainerSpec<?, C> list
6163
}
6264

6365
@Override
64-
public Map<Object, String> getComponentsToRegister() {
65-
return Collections.singletonMap(this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId());
66+
public Map<Object, @Nullable String> getComponentsToRegister() {
67+
return Collections.<Object, @Nullable String>singletonMap(
68+
this.listenerContainerSpec.getObject(), this.listenerContainerSpec.getId());
6669
}
6770

6871
}

spring-integration-core/src/main/java/org/springframework/integration/dsl/ComponentsRegistration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.util.Map;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
/**
2224
* The marker interface for the {@link IntegrationComponentSpec} implementation,
2325
* when there is need to register as beans not only the target spec's components,
@@ -33,6 +35,6 @@
3335
@FunctionalInterface
3436
public interface ComponentsRegistration {
3537

36-
Map<Object, String> getComponentsToRegister();
38+
Map<Object, @Nullable String> getComponentsToRegister();
3739

3840
}

spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static String toJson(Object bean, String... propertiesToExclude) {
8787
stringBuilder.setLength(stringBuilder.length() - 1);
8888
stringBuilder.append("}");
8989
if (stringBuilder.length() == 1) {
90-
return null;
90+
return "{}";
9191
}
9292
else {
9393
return stringBuilder.toString();

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.Arrays;
2222
import java.util.List;
2323

24+
import org.jspecify.annotations.Nullable;
25+
2426
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
2527
import org.springframework.integration.file.filters.CompositeFileListFilter;
2628
import org.springframework.integration.file.filters.FileListFilter;
@@ -39,9 +41,9 @@
3941
*/
4042
public class DefaultDirectoryScanner implements DirectoryScanner {
4143

42-
private volatile FileListFilter<File> filter;
44+
private volatile @Nullable FileListFilter<File> filter;
4345

44-
private volatile FileLocker locker;
46+
private volatile @Nullable FileLocker locker;
4547

4648
/**
4749
* Initialize {@link DefaultDirectoryScanner#filter} with a default list of
@@ -63,7 +65,7 @@ public void setFilter(FileListFilter<File> filter) {
6365
this.filter = filter;
6466
}
6567

66-
protected FileListFilter<File> getFilter() {
68+
protected @Nullable FileListFilter<File> getFilter() {
6769
return this.filter;
6870
}
6971

@@ -72,7 +74,7 @@ public final void setLocker(FileLocker locker) {
7274
this.locker = locker;
7375
}
7476

75-
protected FileLocker getLocker() {
77+
protected @Nullable FileLocker getLocker() {
7678
return this.locker;
7779
}
7880

@@ -82,17 +84,19 @@ protected FileLocker getLocker() {
8284
*/
8385
@Override
8486
public boolean tryClaim(File file) {
85-
return (this.locker == null) || this.locker.lock(file);
87+
FileLocker lockerToUse = this.locker;
88+
return (lockerToUse == null) || lockerToUse.lock(file);
8689
}
8790

8891
@Override
8992
public List<File> listFiles(File directory) throws IllegalArgumentException {
90-
File[] files = listEligibleFiles(directory);
93+
File @Nullable [] files = listEligibleFiles(directory);
9194
if (files == null) {
9295
throw new MessagingException("The path [" + directory
9396
+ "] does not denote a properly accessible directory.");
9497
}
95-
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
98+
FileListFilter<File> filterToUse = this.filter;
99+
return (filterToUse != null) ? filterToUse.filterFiles(files) : Arrays.asList(files);
96100
}
97101

98102
/**
@@ -101,7 +105,7 @@ public List<File> listFiles(File directory) throws IllegalArgumentException {
101105
* @param directory root directory to use for listing
102106
* @return the files this scanner should consider
103107
*/
104-
protected File[] listEligibleFiles(File directory) {
108+
protected File @Nullable [] listEligibleFiles(File directory) {
105109
return directory.listFiles();
106110
}
107111

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
*/
4444
public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implements FileNameGenerator {
4545

46+
@SuppressWarnings("NullAway") // Does not see Nullability on the generics.
4647
private volatile Expression expression =
4748
new FunctionExpression<Message<?>>((message) -> message.getHeaders().get(FileHeaders.FILENAME));
4849

@@ -61,6 +62,7 @@ public void setExpression(String expression) {
6162
* The default is defined by {@link FileHeaders#FILENAME}.
6263
* @param headerName The header name.
6364
*/
65+
@SuppressWarnings("NullAway") // Does not see Nullability on the generics.
6466
public void setHeaderName(String headerName) {
6567
Assert.notNull(headerName, "'headerName' must not be null");
6668
this.expression = new FunctionExpression<Message<?>>((message) -> message.getHeaders().get(headerName));

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@
4343
import java.util.concurrent.atomic.AtomicBoolean;
4444
import java.util.function.Predicate;
4545

46+
import org.jspecify.annotations.Nullable;
47+
4648
import org.springframework.context.Lifecycle;
4749
import org.springframework.integration.endpoint.AbstractMessageSource;
4850
import org.springframework.integration.file.filters.DiscardAwareFileListFilter;
4951
import org.springframework.integration.file.filters.FileListFilter;
5052
import org.springframework.integration.file.filters.ResettableFileListFilter;
5153
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
5254
import org.springframework.integration.support.management.ManageableLifecycle;
53-
import org.springframework.lang.Nullable;
5455
import org.springframework.messaging.Message;
5556
import org.springframework.util.Assert;
5657

@@ -107,6 +108,7 @@ public class FileReadingMessageSource extends AbstractMessageSource<File> implem
107108
*/
108109
private final Queue<File> toBeReceived;
109110

111+
@SuppressWarnings("NullAway.Init")
110112
private File directory;
111113

112114
private DirectoryScanner scanner = new DefaultDirectoryScanner();
@@ -117,9 +119,9 @@ public class FileReadingMessageSource extends AbstractMessageSource<File> implem
117119

118120
private boolean scanEachPoll = false;
119121

120-
private FileListFilter<File> filter;
122+
private @Nullable FileListFilter<File> filter;
121123

122-
private FileLocker locker;
124+
private @Nullable FileLocker locker;
123125

124126
private boolean useWatchService;
125127

@@ -370,7 +372,7 @@ protected void onInit() {
370372
}
371373

372374
@Override
373-
protected AbstractIntegrationMessageBuilder<File> doReceive() {
375+
protected @Nullable AbstractIntegrationMessageBuilder<File> doReceive() {
374376
// rescan only if needed or explicitly configured
375377
if (this.scanEachPoll || this.toBeReceived.isEmpty()) {
376378
scanInputDirectory();
@@ -433,11 +435,19 @@ private final class WatchServiceDirectoryScanner extends DefaultDirectoryScanner
433435

434436
private final ConcurrentMap<Path, WatchKey> pathKeys = new ConcurrentHashMap<>();
435437

438+
private final WatchEvent.Kind<?>[] kinds;
439+
436440
private final Set<File> filesToPoll = ConcurrentHashMap.newKeySet();
437441

442+
@SuppressWarnings("NullAway.Init")
438443
private WatchService watcher;
439444

440-
private WatchEvent.Kind<?>[] kinds;
445+
WatchServiceDirectoryScanner() {
446+
this.kinds =
447+
Arrays.stream(FileReadingMessageSource.this.watchEvents)
448+
.map(watchEventType -> watchEventType.kind)
449+
.toArray(WatchEvent.Kind<?>[]::new);
450+
}
441451

442452
@Override
443453
public void setFilter(FileListFilter<File> filter) {
@@ -451,27 +461,19 @@ public void setFilter(FileListFilter<File> filter) {
451461
public void start() {
452462
try {
453463
this.watcher = FileSystems.getDefault().newWatchService();
464+
Set<File> initialFiles = walkDirectory(FileReadingMessageSource.this.directory.toPath(), null);
465+
initialFiles.addAll(filesFromEvents());
466+
this.filesToPoll.addAll(initialFiles);
454467
}
455468
catch (IOException ex) {
456469
logger.error(ex, () -> "Failed to create watcher for " + FileReadingMessageSource.this.directory);
457470
}
458-
459-
this.kinds = new WatchEvent.Kind<?>[FileReadingMessageSource.this.watchEvents.length];
460-
461-
for (int i = 0; i < FileReadingMessageSource.this.watchEvents.length; i++) {
462-
this.kinds[i] = FileReadingMessageSource.this.watchEvents[i].kind;
463-
}
464-
465-
Set<File> initialFiles = walkDirectory(FileReadingMessageSource.this.directory.toPath(), null);
466-
initialFiles.addAll(filesFromEvents());
467-
this.filesToPoll.addAll(initialFiles);
468471
}
469472

470473
@Override
471474
public void stop() {
472475
try {
473476
this.watcher.close();
474-
this.watcher = null;
475477
this.pathKeys.clear();
476478
}
477479
catch (IOException ex) {
@@ -486,8 +488,6 @@ public boolean isRunning() {
486488

487489
@Override
488490
protected File[] listEligibleFiles(File directory) {
489-
Assert.state(this.watcher != null, "The WatchService hasn't been started");
490-
491491
Set<File> files = new LinkedHashSet<>();
492492

493493
for (Iterator<File> iterator = this.filesToPoll.iterator(); iterator.hasNext(); ) {
@@ -576,7 +576,7 @@ private void processFilesFromOverflowEvent(Set<File> files, WatchEvent<?> event)
576576
}
577577
}
578578

579-
private Set<File> walkDirectory(Path directory, final WatchEvent.Kind<?> kind) {
579+
private Set<File> walkDirectory(Path directory, WatchEvent.@Nullable Kind<?> kind) {
580580
final Set<File> walkedFiles = new LinkedHashSet<>();
581581
try {
582582
registerWatch(directory);

0 commit comments

Comments
 (0)