Monday, September 04, 2017

WatchService to monitor Directories for changes

There a many scenarios where you would like to monitor a directory for new files, or changes to existing files or deleted files. Some of the use cases are
  • Files will be dropped into a directory by a third party which have to be processed by your application
  • A file editor which monitors the directory to make sure the files currently being edited are not modified by another concurrent user.
  • You want to start a build and deploy code whenever a source file is changed (In an enterprise application, this is usually done in a source control in conjunction with a tool like Jenkins).
Starting Java 7, the new java.nio.file.WatchService provides a scalable solution to monitor directory for any changes. In this post, we will go over the features of the WatchService service and a simple example for how to monitor a directory for changes and print the name of the file that was affected.

WatchService Basics

The solution for implementing WatchService to monitor consists of the following main elements
  • WatchService: The actual Service that monitors the registered objects (directories) for changes
  • Watchable: Any object implementing java.nio.file.Watchable interface can be registered with the WatchService. Currenly only implementation for Watchable is java.nio.file.Path
  • WatchKey: A watch key is an object that represents the registration for a Watchable on to a WatchService. A key object is used to signal the application code of any changes to the Directory. A key is created at the time of registration and will be active until one of the following three events occurs
    • The cancel method is invoked on the WatchKey.
    • Cancelled implicitly (object is no longer accessible)
    • Th associated WatchService is closed.

Steps to implement WatchService to monitor a Directory

  1. Create a WatchService: WatchService can be created by invoking newWatchService() method like below
    WatchService watchService = FileSystems.getDefault().newWatchService();
  2. Register the directory which has to be monitored.
    Path dir = Paths.get("c:/test/");
       dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
         StandardWatchEventKinds.ENTRY_MODIFY);
    There are 4 StandardWatchEventKinds
    • ENTRY_CREATE: A directory entry (file/directory) is created
    • ENTRY_DELETE: A directory entry is deleted.
    • ENTRY_MODIFY: A directory entry is modified.
    • OVERFLOW: Indicates that events might have been lost or discarded. OVERFLOW is implicitly registered and need not be registered again.
  3. An infinite loop waiting for events. When an event occurs, the key is signaled and placed into the watcher's queue.
  4. Retrieve the key from the watcher's queue. A key from the watcher's queue can be retrieved by either using the take() or poll() methods on the WatchService. take() waits for an event, where as poll has two versions either to return instantly or wait for a specified time.
    //Wait till next event.
    key = watchService.take();
    //Retrieve the event and remove the next watch key, returns null if no keys are present.
    key = watchService.poll();
    //Wait for 10 minutes for keys, return null after 10 minutes.
    key = watchService.poll(10, TimeUnit.MINUTES);
  5. Retrieve events from key and process. There can be multiple events for a key.
    for (WatchEvent<?> event : key.pollEvents()) {...}
  6. Reset the key using key.reset() method. If the key is not reset(), it will not be able to receive any further events. If the reset() method returns false, it means that the key is not valid any more and you can exit the loop.
  7. Close the service: The watch service exits when either the thread exits or when it is closed (by invoking its closed method).

Full code for a service to monitor a directory for changes and print the name of the file that was affected

In this example, we use the take() method to retrieve the key, and monitor only for StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE and StandardWatchEventKinds.ENTRY_MODIFY events on the directory. The only action we take in case of an event is to print the filename.
package 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;

public class WatchServiceTest {

 public static void main(String[] args) {
  try {
   WatchService watchService = FileSystems.getDefault().newWatchService();

   Path dir = Paths.get("c:/test/");
   dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
     StandardWatchEventKinds.ENTRY_MODIFY);

   for (;;) {

    WatchKey key = null;

    // Retrieve key from WatchService Queue.
    try {
     key = watchService.take();
    } catch (InterruptedException e) {
     return;
    }

    // Process each event for the key.
    for (WatchEvent<?> event : key.pollEvents()) {

     WatchEvent.Kind<?> kind = event.kind();

     // OVERFLOW is implicitly registered.
     if (kind == StandardWatchEventKinds.OVERFLOW) {
      continue;
     }

     WatchEvent<Path> ev = (WatchEvent<Path>) event;

     // WatchEvent.context is the filename and can be used for further processing. In
     // this example, we just print the filename.
     Path filename = ev.context();
     System.out.println(filename + " even " + kind);
    }

    // Reset key to be able to retrieve further events.
    // Break if key is invalid. (reset() returns false.)
    if (!key.reset())
     break;

   }
  } catch (IOException e) {
   e.printStackTrace();
  }

 }

}

No comments:

Post a Comment

Popular Posts