Tuesday, March 19, 2013

JDK7: Explore NIO.2 watch service API & file change notification

Watching objects for file changes and events using thread safe Watch Service API.


JSR 203 is a new java I/O milestone, and the most powerful API ever since the JDK first release, which streamlines all java 7 I/O developments, also it makes I/O applications development easy and powerful more than ever.

The watch service API introduced in java 7 NIO.2 JSR 203 as thread safe service (which) is capable of watching any object for changes and pushes back these changes for you as event notifications. Watched objects should implement java.nio.file.Watchable interface which is the key interface in this API. All the API classes reside in the "java.nio.file" package.

This API is a low level API, which can be used by as it is or you can build your own customized solution that fulfills your requirements. By default it uses the underlying file system features and functionalities to watch registered file system for modification or changes. It allows you to register file, directory or directories to be monitored for different kinds of notification events that you interested in during the registration process.

When one or more of registered events are detected by the watch service, it passes the detected events notifications to the process that responsible for handling the registration process through a separate thread or pool of threads.

Let's clear things up by an example; when you open a file in a text editor like (jEdit, Notepad++, etc.) and if the file is modified by external process outside the editor, the editor will pop up a message box indicating that the file has changed outside the editor and asks you whether to reload the file content because it was modified as shown in (Figure 1). This means that the editor has detected a file change through a watch service, and this service reports back (it) accordingly to the text editor. This is known as "file change notifications" mechanisms. But starting from JDK 7 NIO.2 this service is available through a Watch Service API.

Figure 1: Notepad++ file change notification.

Now as Java 7 introduced the Watch Service API, you no longer need to poll, use in-house solution or external libraries for file system changes. You don't need to implement a separate thread agent the keep track of all contents of the watched directory; to see if anything important has happened.

Regardless the platform (Mac OS X, Linux, UNIX, Solaris or Windows) your application is running on, you have the guarantee that the underlying OS and file system provides the required functionalities that allows your application to receive notifications that has happened on file system as changes.

The Watching Service API Classes:

The java.nio.file.WatchService interface is the starting point combined with three main classes java.nio.file.Watchable, java.nio.file.WatchEvent.Kind<T> and java.nio.file.WatchEvent.Modifier for developing watching capable application. There are multiple implementations for different file systems and platforms. Let's define the functionality of each class as the following:

  1. Watchable Object: Any object is qualified to be watchable if it implements the java.nio.file.Watchable interface.

  2. Event types: Those are the events that you are interested to keep track of their changes, and it is represented by java.nio.file.StandardWatchEventKinds. This class implements the java.nio.file.WatchEvent.Kind interface. The available events are create (WatchEvent.Kind<Path> ENTRY_CREATE), delete (WatchEvent.Kind<Path> ENTRY_DELETE), modify (WatchEvent.Kind<Path> ENTRY_MODIFY), and overflow (WatchEvent.Kind<Object> OVERFLOW).

  3. Registration modifier: This changes the default registration behavior with the Watch Service API. As for now there are no modifiers introduced in NIO.2, it will be implemented in the future releases maybe JDK 8.

  4. Watching service: This is the Watcher that watches the changes done on the file system. And represented by the java.nio.file.WatchService. The created instance from the java.nio.file. FileSystem class runs in the background watching the registered object.

java.nio.file.Watchable interface defines two register methods to register the object with a java.nio.file.WatchService that returns a java.nio.file.WatchKey to represent the registration relationship token between the registered object with a java.nio.file.WatchService. Also an object may be registered with more than one watch service.

The first register method signature is (WatchKey register (WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException;), it receives three arguments the first is the watcher(WatchService) object that is created from the java.nio.file. FileSystem implementation and it is responsible for watching the registered objects for changes and events.

The second Argument is the events (java.nio.file.WatchEvent.Kind<T>) array that you need to watch registered object for like create, add, delete and modify events. The last argument is modifiers (java.nio.file.WatchEvent.Modifier). If any, these modifiers determine how the objects are to be registered with watching service.

The second register method signature is (WatchKey register (WatchService watcher, WatchEvent.Kind<?>[] events) throws IOException;) which receives the first two arguments of the first method without modifiers, and I will use this method in our example.

The will known implementation of the java.nio.file.Watchable interface in NIO.2 is the java.nio.file.Path class and it implements the register methods as we will see (it) in the example below.

Get ready, Pre-requisites:

  1. Download and install Java Development Kit version 7 for your favorite Operating System, download it from here.
  2. Netbeans 7.2+ IDE because it supports JDK 7 (Java SE, EE or version All). And could be downloaded from here.
    1. The lightest one for our case is SE version, download and Install it.

How to implement it:

Now as you have considerable knowledge about Watching Service API so far, so after the long theoretical explanation above, let's do the most interesting part, develop the watching application. This application will watch the create, delete, and modify events for the path "D:\My Documents\My Examples\NetBeans\7.2\WatchableDirectory\src\WatchedFolder" (Or any folder of your choice) and reports the type of event and the file where it occurred. The implementation of any watch application is accomplished through a set of nine steps.

In code Listing 1, all the main steps for developing any watch application are numbered in sequence, and the working mechanism explained in how it works section.

For purposes of testing, try manually to add, delete, or modify a file or directory under this path. Keep in mind that only one level down is monitored (only the "D:\My Documents\My Examples\NetBeans\7.2\WatchableDirectory\src\WatchedFolder" directory), not the entire directory tree under the "D:\My Documents\My Examples\NetBeans\7.2\WatchableDirectory\src\WatchedFolder" directory.

Implementation steps:

  1. Create a new Netbeans project(Ctrl + Shift + N) of type Application project from java category, press next.
  2. In Project name text box name your project "WatchableDirectory", tick the Create main class check box and enter the following "eg.com.tm.wsa.WatchDirectory", your setup should look like the following figure (Figure 2).
    Figure 2: Setting up the application
  3. Hit finish button and your IDE should open as the following figure (Figure 3):

    Figure 3: Created Project.
  4. The following is the full application, select all (Ctrl + A) code inside the already opened class "WatchDirectory" and delete it, then copy and paste the below code in the following list (Listing 1) as it is:

    Listing 1: The Watching Service implementation.
  5. Clean and build the project (Shift + F11) to check that everything is okay, you should see in the output console "BUILD SUCCESSFUL".

Testing the application:

Now we will go through steps for the testing of the application to see the main function of the developed application:

  1. Right click on Source Packages under "WatchableDirectory" project, select New àOther…, under Categories select other and from File Type select Folder, click next, in the Folder Name write "WatchedFolder", and then click finish. This folder we will use for watching its event notifications. The final result should look like the following figure (Figure 4).
    Figure 4: Created "WatchedFolder"

    Note: if you didn't create the folder and tries to run the application you will get the following exception " java.nio.file.NoSuchFileException: D:\My Documents\My Examples\NetBeans\7.2\WatchableDirectory\src\WatchedFolder".

  2. Right click on "WatchableDirectory" project, Run.
  3. Navigate to your created folder "WatchedFolder" and do the following steps and monitor Netbeans output console:
    1. Create new folder "New Folder", you will see "ENTRY_CREATE -> New folder".
    2. Rename the folder to "NIO2-WS API"; you will see "ENTRY_DELETE -> New folder", "ENTRY_CREATE -> NIO2-WS API" Successively.
    3. Create new text document, you will see "ENTRY_CREATE -> New Text Document.txt".
    4. Open the text file in notepad (or any of your favorite text editor), type in the file the following "I am using Java 7 NIO2 WS API."; you will see "ENTRY_MODIFY -> New Text Document.txt".
    5. The final result in the console should look like the following figure (Figure 5).
      Figure 5: "WatchedFolder" notifications monitoring output.
  4. Click stop to stop the application.

After successful testing of the application, when you run this code on Mac, Linux, UNIX, Solaris, or any other OS as I did, it should work like charm, and you will get the same results.

How it works:

For developing any watching capable application, we should first create watch service object, and we accomplish this by calling "FileSystems.getDefault().newWatchService()" as in Listing 1 (Step 1).

This is a resource that should be closed after usage. in code I wrapped it by JDK 7 try() resource (Step 9) feature which takes care of closing any I/O resource that implements java.lang.AutoCloseable interface, therefore you don't have to care about closing any resource because the JVM will take care of that for you.

Now, we have the watch service we need to register the java.nio.file.Path "D:\My Documents\My Examples\NetBeans\7.2\WatchableDirectory\src\WatchedFolder " for create, delete and modify events. Since the Path class implements the Watchable interface so it provides register() method to be used for registration process with watch service, as in Listing 1 (Step 2)

After successful registration, we need to wait for incoming events to process them. The watch service is responsible for signaling any notifications done on the registered object. Then these events are placed in a watcher's queue from where we can retrieve events. We require an infinite loop to accomplish this by using for (;;) {}, or while (true) {} as in Listing 1 (Step 3).

In Listing 1 (Step 4) we get the queued watch key by calling one of the following methods (all of them retrieve the key and remove it from the watcher's service queue):

  1. poll(): If no key is available, it returns immediately a null value.
  2. poll(long, TimeUnit): If no key is available, it waits the specified time and tries again. If still no key is available, then it returns null. The time period is indicated as a long number, while the TimeUnit argument determines whether the specified time is minutes, seconds, milliseconds, or some other unit of time.
  3. take():If no key is available, it waits until a key is queued or the infinite loop is stopped for any of several different reasons.

In our case we use take() method, bear in mind that the key has state that may be one of the following; When the key is first created its status is Ready which means that the key is ready for accepting events.

The second state is Signaled and it means that the key has queued events and they are available to be retrieved by one of poll() or take() methods, and after all events retrieval the key should be reset to return back to its ready state by calling key reset() method as in Listing 1 (Step 8). It returns true if the watch key is valid and has been reset, and false if the watch key could not be reset because it is no longer valid.

The key enters the Invalid state if the folder is not accessible anymore, deleted, watch service closed, or key cancel() method called explicitly. We can check this state by using key isValid() which returns Boolean value indicating the key validity.

In Listing 1 (Step 5) we need to retrieve pending events for a key by calling the key.pollEvents() which returns a list of pending events of type WatchEvent<T>,[where T ] represents an event (or repeated events) for an object that is registered with a watch service.

After we poll all events, we need to get the kind of each event (create, modify, delete)along with its count. This could be done via the WatchEvent.kind() method, which returns the event type as a Kind<T> object as in Listing 1 (Step 6). If you ignore the registered event types, it is possible to receive an OVERFLOW event. This kind of event can be ignored or handled. It is a special event to indicate that events may have been lost or discarded. This event is not needed to be registered for notification as it is used by underlying API.

After getting the event type, get the associated folder or file to process it by getting the event context (the file name is stored as the context of the event). After getting the event type, the event context (the file name is stored as the context of the event) should be used to retrieve the event associated file or folder for processing. Then we can implement our business logic. This is accomplished by calling method WatchEvent.context() as in Listing 1 (Step 7).

Finally We should reset the key and return it to the ready state to receive other events, till we stop our application or the key becomes invalid, and this is done by calling reset() method as described before. And if the key was invalid the infinite loop should be broken; as there is no reason to stay in the infinite loop, Listing 1 (Step 8).

If you forget or fail to call the reset() method, the key will not receive any further events!

Even more:

Let's wrap:

Here you have explored a great facility of NIO.2, the Watch Service API. You learned how to watch a directory for events such as create, delete, and modify. After an overview of this API and an introductory application, you saw how to we implemented the application in nine steps, provided with the test scenario and finally explaining the working mechanism.

Where is the best use of this API? You can combine this API with NIO.2 walks (java.nio.file.FileVisitor API) to watch a directory tree (all directories under the parent directory). Also you can watch video camera surveillance for its input (row image) and process it. These examples were simply meant to stimulate your curiosity to explore further the exciting world of this API. Since it is very versatile, it can be applied in many other scenarios. For example, you might use it to update a file listing in a GUI display or to detect the modification of configuration files that could then be reloaded.

This API is a thread safe, and the watch service can be used to register many watchable objects simultaneously. Often, the file system of the Most operating system implements his own file notification mechanism, and the watch service API makes use of it. But, if there is lack of this implementation, watch service API will poll the file system for event notification.

Finally it is part of the JDK 7. It means that it has a great support, enhancement and improvements in future JDK releases. Earlier you had to use external library for each operating system to support your application, while watch service API is a portable solution.

Project source code:

The final project source code could be downloaded from here

No comments :

Post a Comment