I am trying to build a application that watch a folder and its sub folders to detect file creation or modification. Total files to watch will be growing day by day.
I had tried with java nio WatchService and apache common FileAlterationObserver. WatchService sometimes missing event when file creation/modification happens after WatchKey is taken and before reset. Since FileAlterationObserver is based on polling, when file count is increasing performance is also degrading.
What will be the best approach to build such an application?
Thank you @DuncG. After going through the sample mentioned, I found my solution to my problem.
Adding this sample code if someone facing the same problem.
Here in the example I am adding all the events to a set (this will remove the duplicate events) and process the saved events once the WatchKey is empty. New directories will be registered to WatchService while processing the saved events.
package com.filewatcher;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WatchService implements Runnable {
private static final long POLL_DELAY = 3;
private static final Logger LOGGER = LoggerFactory.getLogger(WatchService.class);
private final WatchService watcher;
private final Map<WatchKey, Path> keys;
private final Set<Path> events = new HashSet<Path>();
public WatchService(Path dir) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey, Path>();
walkAndRegisterDirectories(dir);
}
@Override
public void run() {
while (true) {
try {
WatchKey key;
try {
key = watcher.poll(POLL_DELAY, TimeUnit.SECONDS);
} catch (InterruptedException x) {
return;
}
if (key != null) {
Path root = keys.get(key);
for (WatchEvent<?> event : key.pollEvents()) {
Path eventPath = (Path) event.context();
if (eventPath == null) {
System.out.println(event.kind());
continue;
}
Path fullPath = root.resolve(eventPath);
events.add(fullPath);
}
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
}
} else {
if (events.size() > 0) {
processEvents(events);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Process events and register new directory with watch service
* @param events
* @throws IOException
*/
private void processEvents(Set<Path> events) throws IOException {
for (Path path : events) {
// register directory with watch service if its not already registered
if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) && !this.keys.containsValue(path)) {
registerDirectory(path);
// Since new directory was not registered, get all files inside the directory.
// new/modified files after this will get notified by watch service
File[] files = path.toFile().listFiles();
for (File file : files) {
LOGGER.info(file.getAbsolutePath());
}
} else {
LOGGER.info(path.toString());
}
}
// clear events once processed
events.clear();
}
/**
* Register a directory and its sub directories with watch service
* @param root folder
* @throws IOException
*/
private void walkAndRegisterDirectories(final Path root) throws IOException
{
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
registerDirectory(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Register a directory with watch service
* @param directory
* @throws IOException
*/
private void registerDirectory(Path dir) throws IOException {
WatchKey key = dir.register(this.watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY);
this.keys.put(key, dir);
}
}
public class FileWatcherApplication implements CommandLineRunner {
@Value("${filewatch.folder}")
private String rootPath;
public static void main(String[] args) {
SpringApplication.run(FileWatcherApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
File rootFolder = new File(rootPath);
if (!rootFolder.exists()) {
rootFolder.mkdirs();
}
new Thread(new WatchService(Paths.get(rootPath)), "WatchThread").start();
}
}