This is an automated email from the ASF dual-hosted git repository.

ndimiduk pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.6 by this push:
     new e109c078993 HBASE-28931: Poll SSL cert file for changes (#6381)
e109c078993 is described below

commit e109c07899367eb03bcbfcbfb458f1bb0a05d197
Author: Charles Connell <[email protected]>
AuthorDate: Tue Dec 10 11:33:42 2024 -0500

    HBASE-28931: Poll SSL cert file for changes (#6381)
    
    Signed-off-by: Nick Dimiduk <[email protected]>
---
 .../apache/hadoop/hbase/io/FileChangeWatcher.java  | 112 +++++++-------
 .../hadoop/hbase/io/crypto/tls/X509Util.java       |  48 ++----
 .../hadoop/hbase/io/TestFileChangeWatcher.java     | 172 +++++++--------------
 3 files changed, 115 insertions(+), 217 deletions(-)

diff --git 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
index 70300da45d9..3ac4076766b 100644
--- 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
+++ 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
@@ -18,30 +18,27 @@
 package org.apache.hadoop.hbase.io;
 
 import java.io.IOException;
-import java.nio.file.ClosedWatchServiceException;
-import java.nio.file.FileSystem;
+import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.util.function.Consumer;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.time.Duration;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Instances of this class can be used to watch a directory for file changes. 
When a file is added
- * to, deleted from, or is modified in the given directory, the callback 
provided by the user will
- * be called from a background thread. Some things to keep in mind:
+ * Instances of this class can be used to watch a file for changes. When a 
file's modification time
+ * changes, the callback provided by the user will be called from a background 
thread. Modification
+ * are detected by checking the file's attributes every polling interval. Some 
things to keep in
+ * mind:
  * <ul>
  * <li>The callback should be thread-safe.</li>
  * <li>Changes that happen around the time the thread is started may be 
missed.</li>
  * <li>There is a delay between a file changing and the callback firing.</li>
- * <li>The watch is not recursive - changes to subdirectories will not trigger 
a callback.</li>
  * </ul>
  * <p/>
- * This file has been copied from the Apache ZooKeeper project.
+ * This file was originally copied from the Apache ZooKeeper project, and then 
modified.
  * @see <a href=
  *      
"https://github.com/apache/zookeeper/blob/8148f966947d3ecf3db0b756d93c9ffa88174af9/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileChangeWatcher.java";>Base
  *      revision</a>
@@ -49,9 +46,13 @@ import org.slf4j.LoggerFactory;
 @InterfaceAudience.Private
 public final class FileChangeWatcher {
 
+  public interface FileChangeWatcherCallback {
+    void callback(Path path);
+  }
+
   private static final Logger LOG = 
LoggerFactory.getLogger(FileChangeWatcher.class);
 
-  public enum State {
+  enum State {
     NEW, // object created but start() not called yet
     STARTING, // start() called but background thread has not entered main loop
     RUNNING, // background thread is running
@@ -61,29 +62,29 @@ public final class FileChangeWatcher {
 
   private final WatcherThread watcherThread;
   private State state; // protected by synchronized(this)
+  private FileTime lastModifiedTime;
+  private final Object lastModifiedTimeLock;
+  private final Path filePath;
+  private final Duration pollInterval;
 
   /**
-   * Creates a watcher that watches <code>dirPath</code> and invokes 
<code>callback</code> on
+   * Creates a watcher that watches <code>filePath</code> and invokes 
<code>callback</code> on
    * changes.
-   * @param dirPath  the directory to watch.
+   * @param filePath the file to watch.
    * @param callback the callback to invoke with events. 
<code>event.kind()</code> will return the
    *                 type of event, and <code>event.context()</code> will 
return the filename
    *                 relative to <code>dirPath</code>.
    * @throws IOException if there is an error creating the WatchService.
    */
-  public FileChangeWatcher(Path dirPath, String threadNameSuffix, 
Consumer<WatchEvent<?>> callback)
-    throws IOException {
-    FileSystem fs = dirPath.getFileSystem();
-    WatchService watchService = fs.newWatchService();
-
-    LOG.debug("Registering with watch service: {}", dirPath);
+  public FileChangeWatcher(Path filePath, String threadNameSuffix, Duration 
pollInterval,
+    FileChangeWatcherCallback callback) throws IOException {
+    this.filePath = filePath;
+    this.pollInterval = pollInterval;
 
-    dirPath.register(watchService,
-      new WatchEvent.Kind<?>[] { StandardWatchEventKinds.ENTRY_CREATE,
-        StandardWatchEventKinds.ENTRY_DELETE, 
StandardWatchEventKinds.ENTRY_MODIFY,
-        StandardWatchEventKinds.OVERFLOW });
     state = State.NEW;
-    this.watcherThread = new WatcherThread(threadNameSuffix, watchService, 
callback);
+    lastModifiedTimeLock = new Object();
+    lastModifiedTime = Files.readAttributes(filePath, 
BasicFileAttributes.class).lastModifiedTime();
+    this.watcherThread = new WatcherThread(threadNameSuffix, callback);
     this.watcherThread.setDaemon(true);
   }
 
@@ -91,7 +92,7 @@ public final class FileChangeWatcher {
    * Returns the current {@link FileChangeWatcher.State}.
    * @return the current state.
    */
-  public synchronized State getState() {
+  private synchronized State getState() {
     return state;
   }
 
@@ -187,13 +188,10 @@ public final class FileChangeWatcher {
 
     private static final String THREAD_NAME_PREFIX = "FileChangeWatcher-";
 
-    final WatchService watchService;
-    final Consumer<WatchEvent<?>> callback;
+    final FileChangeWatcherCallback callback;
 
-    WatcherThread(String threadNameSuffix, WatchService watchService,
-      Consumer<WatchEvent<?>> callback) {
+    WatcherThread(String threadNameSuffix, FileChangeWatcherCallback callback) 
{
       super(THREAD_NAME_PREFIX + threadNameSuffix);
-      this.watchService = watchService;
       this.callback = callback;
       setUncaughtExceptionHandler(FileChangeWatcher::handleException);
     }
@@ -201,7 +199,7 @@ public final class FileChangeWatcher {
     @Override
     public void run() {
       try {
-        LOG.info("{} thread started", getName());
+        LOG.debug("{} thread started", getName());
         if (
           !compareAndSetState(FileChangeWatcher.State.STARTING, 
FileChangeWatcher.State.RUNNING)
         ) {
@@ -216,44 +214,40 @@ public final class FileChangeWatcher {
         runLoop();
       } catch (Exception e) {
         LOG.warn("Error in runLoop()", e);
-        throw e;
+        throw new RuntimeException(e);
       } finally {
-        try {
-          watchService.close();
-        } catch (IOException e) {
-          LOG.warn("Error closing watch service", e);
-        }
-        LOG.info("{} thread finished", getName());
+        LOG.debug("{} thread finished", getName());
         FileChangeWatcher.this.setState(FileChangeWatcher.State.STOPPED);
       }
     }
 
-    private void runLoop() {
+    private void runLoop() throws IOException {
       while (FileChangeWatcher.this.getState() == 
FileChangeWatcher.State.RUNNING) {
-        WatchKey key;
-        try {
-          key = watchService.take();
-        } catch (InterruptedException | ClosedWatchServiceException e) {
-          LOG.debug("{} was interrupted and is shutting down...", getName());
-          break;
+        BasicFileAttributes attributes = Files.readAttributes(filePath, 
BasicFileAttributes.class);
+        boolean modified = false;
+        synchronized (lastModifiedTimeLock) {
+          FileTime maybeNewLastModifiedTime = attributes.lastModifiedTime();
+          if (!lastModifiedTime.equals(maybeNewLastModifiedTime)) {
+            modified = true;
+            lastModifiedTime = maybeNewLastModifiedTime;
+          }
         }
-        for (WatchEvent<?> event : key.pollEvents()) {
-          LOG.debug("Got file changed event: {} with context: {}", 
event.kind(), event.context());
+
+        // avoid calling callback while holding lock
+        if (modified) {
           try {
-            callback.accept(event);
+            callback.callback(filePath);
           } catch (Throwable e) {
             LOG.error("Error from callback", e);
           }
         }
-        boolean isKeyValid = key.reset();
-        if (!isKeyValid) {
-          // This is likely a problem, it means that file reloading is broken, 
probably because the
-          // directory we are watching was deleted or otherwise became 
inaccessible (unmounted,
-          // permissions
-          // changed, ???).
-          // For now, we log an error and exit the watcher thread.
-          LOG.error("Watch key no longer valid, maybe the directory is 
inaccessible?");
-          break;
+
+        try {
+          Thread.sleep(pollInterval.toMillis());
+        } catch (InterruptedException e) {
+          LOG.debug("Interrupted", e);
+          Thread.currentThread().interrupt();
+          return;
         }
       }
     }
diff --git 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
index 244f92335fc..4f9d2f5611b 100644
--- 
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
+++ 
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
@@ -20,13 +20,12 @@ package org.apache.hadoop.hbase.io.crypto.tls;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.Security;
 import java.security.cert.PKIXBuilderParameters;
 import java.security.cert.X509CertSelector;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -155,6 +154,8 @@ public final class X509Util {
 
   private static final String[] DEFAULT_CIPHERS_OPENSSL = 
getOpenSslFilteredDefaultCiphers();
 
+  private static final Duration FILE_POLL_INTERVAL = Duration.ofMinutes(1);
+
   /**
    * Not all of our default ciphers are available in OpenSSL. Takes our 
default cipher lists and
    * filters them to only those available in OpenSsl. Prefers TLS 1.3, then 
GCM, then CBC because
@@ -510,50 +511,19 @@ public final class X509Util {
       return null;
     }
     final Path filePath = Paths.get(fileLocation).toAbsolutePath();
-    Path parentPath = filePath.getParent();
-    if (parentPath == null) {
-      throw new IOException("Key/trust store path does not have a parent: " + 
filePath);
-    }
     FileChangeWatcher fileChangeWatcher =
-      new FileChangeWatcher(parentPath, 
Objects.toString(filePath.getFileName()), watchEvent -> {
-        handleWatchEvent(filePath, watchEvent, resetContext);
-      });
+      new FileChangeWatcher(filePath, 
Objects.toString(filePath.getFileName()), FILE_POLL_INTERVAL,
+        watchEventFilePath -> handleWatchEvent(watchEventFilePath, 
resetContext));
     fileChangeWatcher.start();
     return fileChangeWatcher;
   }
 
   /**
    * Handler for watch events that let us know a file we may care about has 
changed on disk.
-   * @param filePath the path to the file we are watching for changes.
-   * @param event    the WatchEvent.
    */
-  private static void handleWatchEvent(Path filePath, WatchEvent<?> event, 
Runnable resetContext) {
-    boolean shouldResetContext = false;
-    Path dirPath = filePath.getParent();
-    if (event.kind().equals(StandardWatchEventKinds.OVERFLOW)) {
-      // If we get notified about possibly missed events, reload the key store 
/ trust store just to
-      // be sure.
-      shouldResetContext = true;
-    } else if (
-      event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)
-        || event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE)
-    ) {
-      Path eventFilePath = dirPath.resolve((Path) event.context());
-      if (filePath.equals(eventFilePath)) {
-        shouldResetContext = true;
-      }
-    }
-    // Note: we don't care about delete events
-    if (shouldResetContext) {
-      LOG.info(
-        "Attempting to reset default SSL context after receiving watch event: 
{} with context: {}",
-        event.kind(), event.context());
-      resetContext.run();
-    } else {
-      if (LOG.isDebugEnabled()) {
-        LOG.debug("Ignoring watch event and keeping previous default SSL 
context. "
-          + "Event kind: {} with context: {}", event.kind(), event.context());
-      }
-    }
+  private static void handleWatchEvent(Path filePath, Runnable resetContext) {
+    LOG.info("Attempting to reset default SSL context after receiving watch 
event on file {}",
+      filePath);
+    resetContext.run();
   }
 }
diff --git 
a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
 
b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
index 3ae23c95bd8..c24e96f8da6 100644
--- 
a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
+++ 
b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
@@ -27,8 +27,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
+import java.nio.file.Path;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -50,7 +50,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This file has been copied from the Apache ZooKeeper project.
+ * This file was originally copied from the Apache ZooKeeper project, but has 
been modified
  * @see <a href=
  *      
"https://github.com/apache/zookeeper/blob/391cb4aa6b54e19a028215e1340232a114c23ed3/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileChangeWatcherTest.java";>Base
  *      revision</a>
@@ -62,20 +62,17 @@ public class TestFileChangeWatcher {
   public static final HBaseClassTestRule CLASS_RULE =
     HBaseClassTestRule.forClass(TestFileChangeWatcher.class);
 
-  private static File tempDir;
   private static File tempFile;
 
   private static final Logger LOG = 
LoggerFactory.getLogger(TestFileChangeWatcher.class);
   private static final HBaseCommonTestingUtility UTIL = new 
HBaseCommonTestingUtility();
 
   private static final long FS_TIMEOUT = 30000L;
+  private static final Duration POLL_INTERVAL = Duration.ofMillis(100);
 
   @BeforeClass
   public static void createTempFile() throws IOException {
-    tempDir = new 
File(UTIL.getDataTestDir(TestFileChangeWatcher.class.getSimpleName()).toString())
-      .getCanonicalFile();
-    FileUtils.forceMkdir(tempDir);
-    tempFile = File.createTempFile("zk_test_", "", tempDir);
+    tempFile = File.createTempFile("zk_test_", "");
   }
 
   @AfterClass
@@ -86,7 +83,7 @@ public class TestFileChangeWatcher {
   @Test
   public void testEnableCertFileReloading() throws IOException {
     Configuration myConf = new Configuration();
-    String sharedPath = "/tmp/foo.jks";
+    String sharedPath = File.createTempFile("foo", 
"foo.jks").getAbsolutePath();
     myConf.set(X509Util.TLS_CONFIG_KEYSTORE_LOCATION, sharedPath);
     myConf.set(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION, sharedPath);
     AtomicReference<FileChangeWatcher> keystoreWatcher = new 
AtomicReference<>();
@@ -94,61 +91,42 @@ public class TestFileChangeWatcher {
     X509Util.enableCertFileReloading(myConf, keystoreWatcher, 
truststoreWatcher, () -> {
     });
     assertNotNull(keystoreWatcher.get());
-    assertThat(keystoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("-foo.jks"));
+    assertThat(keystoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("foo.jks"));
     assertNull(truststoreWatcher.get());
 
     keystoreWatcher.getAndSet(null).stop();
     truststoreWatcher.set(null);
 
-    String truststorePath = "/tmp/bar.jks";
+    String truststorePath = File.createTempFile("bar", 
"bar.jks").getAbsolutePath();
     myConf.set(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION, truststorePath);
     X509Util.enableCertFileReloading(myConf, keystoreWatcher, 
truststoreWatcher, () -> {
     });
 
     assertNotNull(keystoreWatcher.get());
-    assertThat(keystoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("-foo.jks"));
+    assertThat(keystoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("foo.jks"));
     assertNotNull(truststoreWatcher.get());
-    assertThat(truststoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("-bar.jks"));
+    assertThat(truststoreWatcher.get().getWatcherThreadName(), 
Matchers.endsWith("bar.jks"));
 
     keystoreWatcher.getAndSet(null).stop();
     truststoreWatcher.getAndSet(null).stop();
   }
 
   @Test
-  public void testCallbackWorksOnFileChanges() throws IOException, 
InterruptedException {
+  public void testNoFalseNotifications() throws IOException, 
InterruptedException {
     FileChangeWatcher watcher = null;
     try {
-      final List<WatchEvent<?>> events = new ArrayList<>();
-      watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
-        LOG.info("Got an update: {} {}", event.kind(), event.context());
-        // Filter out the extra ENTRY_CREATE events that are
-        // sometimes seen at the start. Even though we create the watcher
-        // after the file exists, sometimes we still get a create event.
-        if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
-          return;
-        }
-        synchronized (events) {
-          events.add(event);
-          events.notifyAll();
+      final List<Path> notifiedPaths = new ArrayList<>();
+      watcher = new FileChangeWatcher(tempFile.toPath(), "test", 
POLL_INTERVAL, path -> {
+        LOG.info("Got an update on path {}", path);
+        synchronized (notifiedPaths) {
+          notifiedPaths.add(path);
+          notifiedPaths.notifyAll();
         }
       });
       watcher.start();
       watcher.waitForState(FileChangeWatcher.State.RUNNING);
       Thread.sleep(1000L); // TODO hack
-      for (int i = 0; i < 3; i++) {
-        LOG.info("Modifying file, attempt {}", (i + 1));
-        FileUtils.writeStringToFile(tempFile, "Hello world " + i + "\n", 
StandardCharsets.UTF_8,
-          true);
-        synchronized (events) {
-          if (events.size() < i + 1) {
-            events.wait(FS_TIMEOUT);
-          }
-          assertEquals("Wrong number of events", i + 1, events.size());
-          WatchEvent<?> event = events.get(i);
-          assertEquals(StandardWatchEventKinds.ENTRY_MODIFY, event.kind());
-          assertEquals(tempFile.getName(), event.context().toString());
-        }
-      }
+      assertEquals("Should not have been notified", 0, notifiedPaths.size());
     } finally {
       if (watcher != null) {
         watcher.stop();
@@ -158,70 +136,32 @@ public class TestFileChangeWatcher {
   }
 
   @Test
-  public void testCallbackWorksOnFileTouched() throws IOException, 
InterruptedException {
-    FileChangeWatcher watcher = null;
-    try {
-      final List<WatchEvent<?>> events = new ArrayList<>();
-      watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
-        LOG.info("Got an update: {} {}", event.kind(), event.context());
-        // Filter out the extra ENTRY_CREATE events that are
-        // sometimes seen at the start. Even though we create the watcher
-        // after the file exists, sometimes we still get a create event.
-        if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
-          return;
-        }
-        synchronized (events) {
-          events.add(event);
-          events.notifyAll();
-        }
-      });
-      watcher.start();
-      watcher.waitForState(FileChangeWatcher.State.RUNNING);
-      Thread.sleep(1000L); // TODO hack
-      LOG.info("Touching file");
-      FileUtils.touch(tempFile);
-      synchronized (events) {
-        if (events.isEmpty()) {
-          events.wait(FS_TIMEOUT);
-        }
-        assertFalse(events.isEmpty());
-        WatchEvent<?> event = events.get(0);
-        assertEquals(StandardWatchEventKinds.ENTRY_MODIFY, event.kind());
-        assertEquals(tempFile.getName(), event.context().toString());
-      }
-    } finally {
-      if (watcher != null) {
-        watcher.stop();
-        watcher.waitForState(FileChangeWatcher.State.STOPPED);
-      }
-    }
-  }
-
-  @Test
-  public void testCallbackWorksOnFileAdded() throws IOException, 
InterruptedException {
+  public void testCallbackWorksOnFileChanges() throws IOException, 
InterruptedException {
     FileChangeWatcher watcher = null;
     try {
-      final List<WatchEvent<?>> events = new ArrayList<>();
-      watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
-        LOG.info("Got an update: {} {}", event.kind(), event.context());
-        synchronized (events) {
-          events.add(event);
-          events.notifyAll();
+      final List<Path> notifiedPaths = new ArrayList<>();
+      watcher = new FileChangeWatcher(tempFile.toPath(), "test", 
POLL_INTERVAL, path -> {
+        LOG.info("Got an update on path {}", path);
+        synchronized (notifiedPaths) {
+          notifiedPaths.add(path);
+          notifiedPaths.notifyAll();
         }
       });
       watcher.start();
       watcher.waitForState(FileChangeWatcher.State.RUNNING);
       Thread.sleep(1000L); // TODO hack
-      File tempFile2 = File.createTempFile("zk_test_", "", tempDir);
-      tempFile2.deleteOnExit();
-      synchronized (events) {
-        if (events.isEmpty()) {
-          events.wait(FS_TIMEOUT);
+      for (int i = 0; i < 3; i++) {
+        LOG.info("Modifying file, attempt {}", (i + 1));
+        FileUtils.writeStringToFile(tempFile, "Hello world " + i + "\n", 
StandardCharsets.UTF_8,
+          true);
+        synchronized (notifiedPaths) {
+          if (notifiedPaths.size() < i + 1) {
+            notifiedPaths.wait(FS_TIMEOUT);
+          }
+          assertEquals("Wrong number of notifications", i + 1, 
notifiedPaths.size());
+          Path path = notifiedPaths.get(i);
+          assertEquals(tempFile.getPath(), path.toString());
         }
-        assertFalse(events.isEmpty());
-        WatchEvent<?> event = events.get(0);
-        assertEquals(StandardWatchEventKinds.ENTRY_CREATE, event.kind());
-        assertEquals(tempFile2.getName(), event.context().toString());
       }
     } finally {
       if (watcher != null) {
@@ -232,35 +172,29 @@ public class TestFileChangeWatcher {
   }
 
   @Test
-  public void testCallbackWorksOnFileDeleted() throws IOException, 
InterruptedException {
+  public void testCallbackWorksOnFileTouched() throws IOException, 
InterruptedException {
     FileChangeWatcher watcher = null;
     try {
-      final List<WatchEvent<?>> events = new ArrayList<>();
-      watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
-        LOG.info("Got an update: {} {}", event.kind(), event.context());
-        // Filter out the extra ENTRY_CREATE events that are
-        // sometimes seen at the start. Even though we create the watcher
-        // after the file exists, sometimes we still get a create event.
-        if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
-          return;
-        }
-        synchronized (events) {
-          events.add(event);
-          events.notifyAll();
+      final List<Path> notifiedPaths = new ArrayList<>();
+      watcher = new FileChangeWatcher(tempFile.toPath(), "test", 
POLL_INTERVAL, path -> {
+        LOG.info("Got an update on path {}", path);
+        synchronized (notifiedPaths) {
+          notifiedPaths.add(path);
+          notifiedPaths.notifyAll();
         }
       });
       watcher.start();
       watcher.waitForState(FileChangeWatcher.State.RUNNING);
       Thread.sleep(1000L); // TODO hack
-      tempFile.delete();
-      synchronized (events) {
-        if (events.isEmpty()) {
-          events.wait(FS_TIMEOUT);
+      LOG.info("Touching file");
+      FileUtils.touch(tempFile);
+      synchronized (notifiedPaths) {
+        if (notifiedPaths.isEmpty()) {
+          notifiedPaths.wait(FS_TIMEOUT);
         }
-        assertFalse(events.isEmpty());
-        WatchEvent<?> event = events.get(0);
-        assertEquals(StandardWatchEventKinds.ENTRY_DELETE, event.kind());
-        assertEquals(tempFile.getName(), event.context().toString());
+        assertFalse(notifiedPaths.isEmpty());
+        Path path = notifiedPaths.get(0);
+        assertEquals(tempFile.getPath(), path.toString());
       }
     } finally {
       if (watcher != null) {
@@ -276,8 +210,8 @@ public class TestFileChangeWatcher {
     FileChangeWatcher watcher = null;
     try {
       final AtomicInteger callCount = new AtomicInteger(0);
-      watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
-        LOG.info("Got an update: {} {}", event.kind(), event.context());
+      watcher = new FileChangeWatcher(tempFile.toPath(), "test", 
POLL_INTERVAL, path -> {
+        LOG.info("Got an update for path {}", path);
         int oldValue;
         synchronized (callCount) {
           oldValue = callCount.getAndIncrement();

Reply via email to