package org.peerbox.watchservice; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractWatchService { private static final Logger logger = LoggerFactory.getLogger(AbstractWatchService.class); private final List<ILocalFileEventListener> eventListeners; private final BlockingQueue<INotifyFileEvent> eventQueue; private Thread notifierThread; private Path folderToWatch; protected final AtomicBoolean isRunning; public AbstractWatchService() { super(); isRunning = new AtomicBoolean(false); this.eventListeners = new CopyOnWriteArrayList<ILocalFileEventListener>(); this.eventQueue = new LinkedBlockingQueue<INotifyFileEvent>(); } public final void start(final Path folderToWatch) throws Exception { if (folderToWatch == null) { throw new IllegalArgumentException("Parameter folderToWatch must not be null."); } if (!Files.exists(folderToWatch)) { throw new IllegalArgumentException("The folder folderToWatch does not exist."); } if (!Files.isDirectory(folderToWatch)) { throw new IllegalArgumentException("Parameter folderToWatch must not point to a file."); } this.folderToWatch = folderToWatch; eventQueue.clear(); notifierThread = new Thread(new EventListenerNotifier()); notifierThread.setName("WatchServiceNotifier"); notifierThread.start(); onStarted(); isRunning.set(true); } protected abstract void onStarted() throws Exception; public final void stop() throws Exception { isRunning.set(false); if (notifierThread != null) { notifierThread.interrupt(); notifierThread = null; } eventQueue.clear(); onStopped(); } protected abstract void onStopped() throws Exception; public synchronized void addFileEventListener(final ILocalFileEventListener listener) { eventListeners.add(listener); } public synchronized void removeFileEventListener(final ILocalFileEventListener listener) { eventListeners.remove(listener); } protected Path getFolderToWatch() { return folderToWatch; } private void notifyFileCreated(final Path path) { for (ILocalFileEventListener l : eventListeners) { l.onLocalFileCreated(path); } } private void notifyFileModified(final Path path) { for (ILocalFileEventListener l : eventListeners) { l.onLocalFileModified(path); } } private void notifyFileDeleted(final Path path) { for (ILocalFileEventListener l : eventListeners) { l.onLocalFileDeleted(path); } } protected void addNotifyEvent(final INotifyFileEvent event) throws InterruptedException { eventQueue.put(event); } private class EventListenerNotifier implements Runnable { @Override public void run() { processEventQueue(); logger.info("Notifier thread exiting."); } private void processEventQueue() { while (true) { try { INotifyFileEvent event = eventQueue.take(); event.notifyEventListeners(); } catch (InterruptedException iex) { if (isRunning.get()) { // stop not called - unexpected! logger.warn("Notifier thread interrupted unexpectedly. Stop sending notifications to event listeners."); } else { // if stop() called, notifier thread gets interrupted logger.trace("Stop processing event queue (stop sending notifications to event listeners)."); } return; } catch (Exception ex) { logger.warn("Exception catched: {}", ex.getMessage(), ex); } } } } protected interface INotifyFileEvent { void notifyEventListeners(); void logEvent(); } protected class NotifyFileCreated implements INotifyFileEvent { private final Path path; public NotifyFileCreated(Path path) { this.path = path; } @Override public void notifyEventListeners() { notifyFileCreated(path); } @Override public void logEvent() { logger.debug("Notify CREATED - {}", path); } } protected class NotifyFileModified implements INotifyFileEvent { private final Path path; public NotifyFileModified(Path path) { this.path = path; } @Override public void notifyEventListeners() { notifyFileModified(path); } @Override public void logEvent() { logger.debug("Notify MODIFIED - {}", path); } } protected class NotifyFileDeleted implements INotifyFileEvent { private final Path path; public NotifyFileDeleted(Path path) { this.path = path; } @Override public void notifyEventListeners() { notifyFileDeleted(path); } @Override public void logEvent() { logger.debug("Notify DELETED - {}", path); } } }