package org.sef4j.core.helpers.files; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import org.sef4j.core.helpers.tasks.PollingEventProvider.AbstractPollingEventProvider; import org.sef4j.core.util.IStartableSupport; import org.sef4j.core.util.factorydef.AbstractSharedObjByDefFactory; import org.sef4j.core.util.factorydef.DependencyObjectCreationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * java.nio helper class for watching on dir (not file in jdk?!), and convert calls to poll() into EventSender.sendEvent() * * <PRE> * * start() = register watch * -----> * <---addEventListener * poll * -----> +------------------+ * poll |filePath | * -----> |eventListeners | ---> sendEvent() * poll |java.nio.watch | * -----> +------------------+ ---> sendEvent() * poll * -----> * <---removeEventListener * stop = unregister watch * -----> * * </PRE> * */ public class ChangedFileWatchPollingEventProvider extends AbstractPollingEventProvider<FileChangeEvent> implements IStartableSupport { private static final Logger LOG = LoggerFactory.getLogger(ChangedFileWatchPollingEventProvider.class); private Path watchPath; private WatchService watchService; private WatchKey watchKey; // ------------------------------------------------------------------------ public ChangedFileWatchPollingEventProvider(Path watchPath) { super("FileWatch"); this.watchPath = watchPath; } // ------------------------------------------------------------------------ @Override public boolean isStarted() { return watchKey != null; } @Override public void start() { try { watchService = FileSystems.getDefault().newWatchService(); } catch (IOException ex) { LOG.error("Failed to wath for file change '" + watchPath + "': can not get file WatchService", ex); return; } try { watchKey = watchPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); } catch (IOException ex) { LOG.error("Failed to wath for file change '" + watchPath + "': can not get file WatchService", ex); return; } } @Override public void stop() { if (watchKey != null) { try { watchKey.cancel(); } catch(Exception ex) { LOG.error("Failed to unregister file watch? .. ignore, no rethrow!", ex); } watchKey = null; watchService = null; } } @Override public void poll() { if (watchKey == null) { return; } // get event if any is present, or null if none (non blocking) WatchKey polledKey = watchService.poll(); if (polledKey == null) { return; } for (WatchEvent<?> event : polledKey.pollEvents()) { // get event type WatchEvent.Kind<?> kind = event.kind(); // get file name @SuppressWarnings("unchecked") WatchEvent<Path> ev = (WatchEvent<Path>) event; Path relativeChangedPath = ev.context(); Path changedPath = watchPath.resolve(relativeChangedPath); LOG.debug("detected watch file change:" + kind.name() + ": " + changedPath); if (kind == StandardWatchEventKinds.OVERFLOW) { continue; } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY || kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_DELETE) { FileChangeEvent fileChangeEvent = new FileChangeEvent( changedPath.toString(), kind); // *** sendEvent *** super.sendEvent(fileChangeEvent); } watchKey.reset(); } } // ------------------------------------------------------------------------ public static class Factory extends AbstractSharedObjByDefFactory<ChangedFileWatchPollingEventProviderDef,ChangedFileWatchPollingEventProvider> { public Factory() { super("ChangedFileWatchPollingEventProvider", ChangedFileWatchPollingEventProviderDef.class); } @Override public ChangedFileWatchPollingEventProvider create(ChangedFileWatchPollingEventProviderDef def, DependencyObjectCreationContext ctx) { return new ChangedFileWatchPollingEventProvider(Paths.get(def.getFilePath())); } } }