Apache Commons IO FileAlterationMonitor

Introduction

In the last post, I wrote about the Apache Commons IO FileAlterationObserver  There we used a FileAlterationListener registered on a FileAlterationObserver to get file system change notifications. To run the observer, we used a custom Runnable class. But the Apache Commons IO library provides this functionality. More specifically, we have the Apache Commons IO FileAlterationMonitor class. It is a runnable which creates a new thread from which it calls the FileAlterationObserver at the specified interval.

In this post, let us learn about the FileAlterationMonitor class and how to use it with the FileAlterationObserver we learnt in the previous post.

I won’t be explaining how FileAlterationObserver and FileAlterationListener works in this post. Please read the previous post first if you haven’t or not already aware of these.

Apache Commons IO FileAlterationMonitor

A FileAlterationMonitor is a Runnable. We create an instance of FileAlterationMonitor and register one or more of FileAlterationObservers to it. We will also configure the time interval at which to notify the observer(s). Once we start the monitor, it creates a new thread which will notify the registered observers at specified time interval with the file system changes.

Remember that we can add one or more listeners to an observer. Here, we can attach one or more observers to the FileAlterationMonitor. Hence, the file system updates will go to each of the listeners for each of the observers added to the FileAlterationMonitor instance.

FileAlterationMonitor
FileAlterationMonitor

FileAlterationMonitor – To trigger the FileAlterationObserver(s)

As in the last post, I’ll be using our implementation of FileAlterationListener. And create a FileAlterationObserver for the root directory /tmp.

// Refer to the linked previous post for the FileChangesListener class
FileAlterationListener fileAlterationListener = new FileChangesListener();

String directory = "/tmp";
FileAlterationObserver fileAlterationObserver = new FileAlterationObserver(directory);
fileAlterationObserver.addListener(fileAlterationListener);

In the last post, we used a custom Runnable class to call the FileAlterationObserver’s checkAndNotify method. Let us now use the FileAlterationMonitor for that.

FileAlterationMonitor creation

First, we will create an instance of FileAlterationMonitor to run once every 5 seconds. The one-argument constructor takes the time interval at which to call the observer as an argument. Second, we add the created fileAlterationObserver instance as an observer (we can add any number of observers). Finally, we call the start() method of the monitor.

FileAlterationMonitor fileAlterationMonitor = new FileAlterationMonitor(5000);
fileAlterationMonitor.addObserver(fileAlterationObserver);

//Throws exception
// it will initialize the fileAlterationObserver
fileAlterationMonitor.start();

Calling the start method does two things:

  1. For each of the fileAlterationObservers, it will call the initialize() method on it (I have explained why this is required in the previous post on FileAlterationObserver).
  2. It creates a new thread passing itself as an argument (runnable) and starts it.

FileAlterationMonitor – run method

Since a FileAlterationMonitor is a Runnable, its run() method has the core logic. It has a tight while loop which does two things:

  1. For each of the observers, it calls their checkAndNotify() method.
  2. Sleeps for interval amount of time.

Stopping a FileAlterationMonitor

To stop a FileAlterationMonitor (say when we no longer want it to watch the file system), we can call its stop() method. It has two overloads – one taking stopInterval argument (a long value) and one without any arguments. When passed the stopInterval, it denotes the amount of time in milliseconds that it will wait for the threads to finish. Passing 0 will make it wait till the thread is finished. If the threads don’t finish by the specified timeout, it returns and the thread continues to run. Implementation detail: It passes this timeout value to the Thread.join() method.

It does the following:

  1. It calls interrupt() on the thread.
  2. Then it calls join() on the thread.
  3. Finally, it calls the destroy() method on each of the observers.

Watching file and directory updates – code run

Starting the FileAlterationMonitor

Let us start the created FileAlterationMonitor and end it (calling stop()) after 20 seconds. 

// same as above code
fileAlterationMonitor.start();

// Run for 20 secs and stop it
Thread.sleep(20000);
fileAlterationMonitor.stop();

To know the thread in which it is running, let us add the thread name in the print message for the FileChangesListener#onStart method. (I didn’t add the thread name in the other print messages as it would be the same).

public class FileChangesListener implements FileAlterationListener {
    @Override
    public void onStart(FileAlterationObserver observer) {
        System.out.println("onStart called for observer " + observer + " on thread: " + Thread.currentThread().getName());
    }
    //rest of the methods
}

When we start the file alteration monitor, we get the onStart and onEnd methods called.

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

From the above print, we can see it is not the main thread; it ran on Thread-0. Remember the chain of calls happening:

FileAlterationMonitor -> FileAlterationObserver -> FileAlterationListener

The FileAlterationMonitor calls the checkAndNotify() of the FileAlterationObserver and it calls all the registered FileAlterationListener’s method.

File system updates

While the monitor is running, let us create/delete/change files and directories to see the notifications we get on the listener.

File changes

Creating a file,

touch file1.txt

We get,

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
File created /tmp/file1.txt
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Updating the file by writing some data into it, we get,

echo "data">file1.txt
onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
File changed /tmp/file1.txt
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Finally, deleting the file, we get the below.

rm file1.txt
onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
File deleted /tmp/file1.txt
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Directory changes

Now, let us create a directory and add some files into it. And then we will change one of the files. Finally, we will delete the directory.

Begin, by creating a directory called newDirectory.

mkdir newDirectory

We get,

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
Directory created /tmp/newDirectory
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Create two files in the directory.

touch newDirectory/file2.txt
touch newDirectory/file3.txt

We get the directory changed event and file created for the created files.

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
Directory changed /tmp/newDirectory
File created /tmp/newDirectory/file2.txt
File created /tmp/newDirectory/file3.txt
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Updating one of the files, we get the file changed event.

echo "data" >> newDirectory/file2.txt
onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
File changed /tmp/newDirectory/file2.txt
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Finally, let us delete the directory. We will get the file deleted and the directory deleted events.

rm -r newDirectory
onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
File deleted /tmp/newDirectory/file2.txt
File deleted /tmp/newDirectory/file3.txt
Directory deleted /tmp/newDirectory
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Other varieties of FileAlterationMonitor

Let us look at other overloaded constructors in the FileAlterationMonitor class.

There is a default constructor (no-argument constructor). When using it, it sets the default interval as 10 seconds.

We can also pass one or more of FileAlterationObservers after passing the time interval (as a var-args or as a collection).

FileAlterationListener fileAlterationListener = new FileChangesListener();

String directory = "/tmp";
FileAlterationObserver fileAlterationObserver = new FileAlterationObserver(directory);
fileAlterationObserver.addListener(fileAlterationListener);

FileAlterationMonitor fileAlterationMonitor = new FileAlterationMonitor(5000, fileAlterationListener);

When doing it this way, we can skip the call to addObserver.

Using a custom thread factory

There is a setThreadFactory method where we can pass a custom ThreadFactory. A ThreadFactory object is used to create threads when needed. For example, let us create a ThreadFactory which creates thread with a custom name.

public class MyCustomThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("MyThread");
        return thread;
    }
}

We can set to use this as:

fileAlterationMonitor.setThreadFactory(new MyCustomThreadFactory());

Now, when running the monitor, we can see the name of the thread as shown below.

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: MyThread

Miscellaneous points on FileAlterationMonitor

Before wrapping up, there are a few points to note.

  1. We cannot call the start() method more than once. Doing so will throw an IllegalStateException.
  2. We can also call the run() method directly (We can do this only after calling start(); else there is no effect). 
    1. This will cause it to run the monitor on the main thread itself i.e., in addition to the running thread, the file alteration observer will start to run on the main thread (i.e., the thread from which we call run()). 
  3. We can remove observer using the removeObserver() method.
fileAlterationMonitor.start();
fileAlterationMonitor.run(); //runs on main thread

Prints,

onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: Thread-0
onStart called for observer FileAlterationObserver[file='/tmp', listeners=1] on thread: main
onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

onStop called for observer FileAlterationObserver[file='/tmp', listeners=1]

Conclusion

This post is a continuation of the previous post. We learnt about the Apache Commons IO FileAlterationMonitor class and how it works with the FileAlterationObserver and FileAlterationListener.

And please make sure to follow me on Twitter and/or subscribe to my newsletter to get future post updates. And check out the other apache-commons posts as well.

This Post Has One Comment

  1. Deceptio

    A really great article! Above all, very detailed and precisely described. Many Thanks

Leave a Reply