0

what I would like to do is to track the files removed and apply certain logic around this (get the id and update the entities). I've found that we can pass a list of watch events inside the channel adapter including

FileReadingMessageSource.WatchEventType.DELETE

but when I remove the file from the folder I do not see any events triggered and the transformer is never being applied

@Bean
public IntegrationFlow integrationFlow(FileToMovieTransformer fileToMovieTransformer) {

    return this.integrationFlowBuilder()
            .transform(fileToMovieTransformer)
            .channel(movieHandlerChannel())
            .get();

}

    private IntegrationFlowBuilder integrationFlowBuilder() {

    return IntegrationFlows.from(

            Files.inboundAdapter(new File(localFilmFolder))
                    .autoCreateDirectory(true)
                    .useWatchService(true)
                    .watchEvents(FileReadingMessageSource.WatchEventType.CREATE, FileReadingMessageSource.WatchEventType.DELETE)
                    .patternFilter("*.xml"),

            e -> e.poller(Pollers.fixedDelay(10, TimeUnit.SECONDS)
            ));
}
hdmiimdh
  • 384
  • 6
  • 19

2 Answers2

1

I would say that you treat DELETE wrong way:

/**
 * Directory entry deleted.
 *
 * <p> When a directory is registered for this event then the {@link WatchKey}
 * is queued when it is observed that an entry is deleted or renamed out of
 * the directory. The event {@link WatchEvent#count count} for this event
 * is always {@code 1}.
 */
public static final WatchEvent.Kind<Path> ENTRY_DELETE =
    new StdWatchEventKind<Path>("ENTRY_DELETE", Path.class);

So, there is already nothing to emit as a message to downstream. We definitely talk here about a FileReadingMessageSource. But with DELETE there is nothing to read any more. Am I missing anything?

And here is what we have in the Docs so far:

The ENTRY_DELETE events have effect for the ResettableFileListFilter implementations and, therefore, their files are provided for the remove() operation. This means that (when this event is enabled), filters such as the AcceptOnceFileListFilter will have the file removed, meaning that, if a file with the same name appears, it will pass the filter and be sent as a message.

Therefore to achieve whatever you would like to do in case of DELETE event, you need to implement ResettableFileListFilter and together with SimplePatternFileListFilter you should composite them into the CompositeFileListFilter.

When file is deleted , that DELETE event is emitted and we end up with the logic like:

if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
    if (getFilter() instanceof ResettableFileListFilter) {
        ((ResettableFileListFilter<File>) getFilter()).remove(file);
    }

Where the mentioned CompositeFileListFilter definitely implements this ResettableFileListFilter one and will delegate to your own implementation.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • thanks a lot for your explanation, clear enough, except the way to expand the composite filter with my new implementation of the ResettableFileListFilter without having any effect on the pattern i use .patternFilter("*.xml") an example or any references will be very much appreciated? – hdmiimdh Apr 19 '18 at 13:32
  • There is a `.filter()` for the `Files.inboundAdapter()` option instead of the mentioned `.patternFilter("*.xml")`. That's where you will inject that `CompositeFileListFilter`. The `.patternFilter("*.xml")` will go in this case to the `SimplePatternFileListFilter`: https://docs.spring.io/spring-integration/docs/5.0.4.RELEASE/reference/html/files.html#file-reading – Artem Bilan Apr 19 '18 at 13:43
  • thanks, got it completely, very useful will post code when check it out – hdmiimdh Apr 19 '18 at 13:53
1

thanks for @Artem, here is the complete code sample that seems to work well for me

    private IntegrationFlowBuilder integrationFlowBuilder() {

    final List<FileListFilter<File>> defaultFilters = new ArrayList<>(2);

    defaultFilters.add(new IgnoreHiddenFileListFilter());
    defaultFilters.add(new AcceptOnceFileListFilter<>());
    defaultFilters.add(new SimplePatternFileListFilter("*.xml"));
    defaultFilters.add(myCustomRemovalFilter);

    CompositeFileListFilter fileListFilter = new CompositeFileListFilter<>(defaultFilters);

    return IntegrationFlows.from(

            Files.inboundAdapter(new File(localFilmFolder))
                    .autoCreateDirectory(true)
                    .filter(fileListFilter)
                    .useWatchService(true)
                    .watchEvents(FileReadingMessageSource.WatchEventType.CREATE, FileReadingMessageSource.WatchEventType.DELETE),

            e -> e.poller(Pollers.fixedDelay(10, TimeUnit.SECONDS)
            ));
} 

and the filter looks like

@Component
public class MyCustomRemovalFilter implements ResettableFileListFilter<File> {

private static final Logger LOGGER = LogManager.getLogger(MyCustomRemovalFilter.class);

@Override
public boolean remove(File xmlFile) {

    if (xmlFile == null) {
        return true;
    }
    // TODO you own on removal logic 
}

@Override
public List<File> filterFiles(File[] files) {

    if (files == null || files.length == 0) {
        return Collections.emptyList();
    }
    return Arrays.asList(files);
}
}
hdmiimdh
  • 384
  • 6
  • 19