View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.impl;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertTrue;
21  
22  import java.io.BufferedWriter;
23  import java.io.File;
24  import java.io.IOException;
25  import java.nio.file.Files;
26  import java.util.concurrent.atomic.AtomicLong;
27  
28  import org.apache.commons.vfs2.AbstractVfsTestCase;
29  import org.apache.commons.vfs2.FileChangeEvent;
30  import org.apache.commons.vfs2.FileListener;
31  import org.apache.commons.vfs2.FileObject;
32  import org.apache.commons.vfs2.FileSystemManager;
33  import org.apache.commons.vfs2.VFS;
34  import org.junit.After;
35  import org.junit.Before;
36  import org.junit.BeforeClass;
37  import org.junit.Ignore;
38  import org.junit.Test;
39  
40  /**
41   * Tests {@link DefaultFileMonitor}.
42   */
43  public class DefaultFileMonitorTest {
44  
45      private static class CountingListener implements FileListener {
46          private final AtomicLong created = new AtomicLong();
47          private final AtomicLong changed = new AtomicLong();
48          private final AtomicLong deleted = new AtomicLong();
49  
50          @Override
51          public void fileChanged(final FileChangeEvent event) {
52              changed.incrementAndGet();
53          }
54  
55          @Override
56          public void fileCreated(final FileChangeEvent event) {
57              created.incrementAndGet();
58          }
59  
60          @Override
61          public void fileDeleted(final FileChangeEvent event) {
62              deleted.incrementAndGet();
63          }
64      }
65  
66      private enum Status {
67          CHANGED, DELETED, CREATED
68      }
69  
70      private class TestFileListener implements FileListener {
71          @Override
72          public void fileChanged(final FileChangeEvent event) throws Exception {
73              status = Status.CHANGED;
74          }
75  
76          @Override
77          public void fileCreated(final FileChangeEvent event) throws Exception {
78              status = Status.CREATED;
79          }
80  
81          @Override
82          public void fileDeleted(final FileChangeEvent event) throws Exception {
83              status = Status.DELETED;
84          }
85      }
86  
87      private static final int DELAY_MILLIS = 100;
88  
89      private FileSystemManager fileSystemManager;
90  
91      private File testDir;
92  
93      private volatile Status status;
94  
95      private File testFile;
96  
97      private void deleteTestFileIfPresent() {
98          if (testFile != null && testFile.exists()) {
99              final boolean deleted = testFile.delete();
100             assertTrue(testFile.toString(), deleted);
101         }
102     }
103 
104     /**
105      * VFS-299: Handlers are not removed. One instance is {@link DefaultFileMonitor#removeFile(FileObject)}.
106      *
107      * As a result, the file monitor will fire two created events.
108      */
109     @Ignore("VFS-299")
110     @Test
111     public void ignore_testAddRemove() throws Exception {
112         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toString())) {
113             final CountingListener listener = new CountingListener();
114             final DefaultFileMonitor monitor = new DefaultFileMonitor(listener);
115             monitor.setDelay(DELAY_MILLIS);
116             try {
117                 monitor.addFile(fileObject);
118                 monitor.removeFile(fileObject);
119                 monitor.addFile(fileObject);
120                 monitor.start();
121                 writeToFile(testFile);
122                 Thread.sleep(DELAY_MILLIS * 3);
123                 assertEquals("Created event is only fired once", 1, listener.created.get());
124             } finally {
125                 monitor.stop();
126             }
127         }
128     }
129 
130     /**
131      * VFS-299: Handlers are not removed. There is no API for properly decommissioning a file monitor.
132      *
133      * As a result, listeners of stopped monitors still receive events.
134      */
135     @Ignore("VFS-299")
136     @Test
137     public void ignore_testStartStop() throws Exception {
138         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toString())) {
139             final CountingListener stoppedListener = new CountingListener();
140             final DefaultFileMonitor stoppedMonitor = new DefaultFileMonitor(stoppedListener);
141             stoppedMonitor.start();
142             try {
143                 stoppedMonitor.addFile(fileObject);
144             } finally {
145                 stoppedMonitor.stop();
146             }
147 
148             // Variant 1: it becomes documented behavior to manually remove all files after stop() such that all
149             // listeners
150             // are removed
151             // This currently does not work, see DefaultFileMonitorTests#testAddRemove above.
152             // stoppedMonitor.removeFile(file);
153 
154             // Variant 2: change behavior of stop(), which then removes all handlers.
155             // This would remove the possibility to pause watching files. Resuming watching for the same files via
156             // start();
157             // stop(); start(); would not work.
158 
159             // Variant 3: introduce new method DefaultFileMonitor#close which definitely removes all resources held by
160             // DefaultFileMonitor.
161 
162             final CountingListener activeListener = new CountingListener();
163             final DefaultFileMonitor activeMonitor = new DefaultFileMonitor(activeListener);
164             activeMonitor.setDelay(DELAY_MILLIS);
165             activeMonitor.addFile(fileObject);
166             activeMonitor.start();
167             try {
168                 writeToFile(testFile);
169                 Thread.sleep(DELAY_MILLIS * 10);
170 
171                 assertEquals("The listener of the active monitor received one created event", 1, activeListener.created.get());
172                 assertEquals("The listener of the stopped monitor received no events", 0, stoppedListener.created.get());
173             } finally {
174                 activeMonitor.stop();
175             }
176         }
177     }
178 
179     @Before
180     public void setUp() throws Exception {
181         fileSystemManager = VFS.getManager();
182         testDir = AbstractVfsTestCase.getTestDirectoryFile();
183         status = null;
184         testFile = new File(testDir, "testReload.properties");
185         deleteTestFileIfPresent();
186     }
187 
188     @After
189     public void tearDown() {
190         // fileSystemManager.close();
191         deleteTestFileIfPresent();
192     }
193 
194     @Test
195     public void testChildFileDeletedWithoutRecursiveChecking() throws Exception {
196         writeToFile(testFile);
197         try (final FileObject fileObject = fileSystemManager.resolveFile(testDir.toURI().toURL().toString())) {
198             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
199             monitor.setDelay(2000);
200             monitor.setRecursive(false);
201             monitor.addFile(fileObject);
202             monitor.start();
203             try {
204                 status = null;
205                 Thread.sleep(DELAY_MILLIS * 5);
206                 testFile.delete();
207                 Thread.sleep(DELAY_MILLIS * 30);
208                 assertEquals("Event should not have occurred", null, status);
209             } finally {
210                 monitor.stop();
211             }
212         }
213     }
214 
215     @Test
216     public void testChildFileRecreated() throws Exception {
217         writeToFile(testFile);
218         try (final FileObject fileObj = fileSystemManager.resolveFile(testDir.toURI().toURL().toString())) {
219             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
220             monitor.setDelay(2000);
221             monitor.setRecursive(true);
222             monitor.addFile(fileObj);
223             monitor.start();
224             try {
225                 status = null;
226                 Thread.sleep(DELAY_MILLIS * 5);
227                 testFile.delete();
228                 waitFor(Status.DELETED, DELAY_MILLIS * 30);
229                 status = null;
230                 Thread.sleep(DELAY_MILLIS * 5);
231                 writeToFile(testFile);
232                 waitFor(Status.CREATED, DELAY_MILLIS * 30);
233             } finally {
234                 monitor.stop();
235             }
236         }
237     }
238 
239     @Test
240     public void testFileCreated() throws Exception {
241         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toURL().toString())) {
242             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
243             // TestFileListener manipulates status
244             monitor.setDelay(DELAY_MILLIS);
245             monitor.addFile(fileObject);
246             monitor.start();
247             try {
248                 writeToFile(testFile);
249                 Thread.sleep(DELAY_MILLIS * 5);
250                 waitFor(Status.CREATED, DELAY_MILLIS * 5);
251             } finally {
252                 monitor.stop();
253             }
254         }
255     }
256 
257     @Test
258     public void testFileDeleted() throws Exception {
259         writeToFile(testFile);
260         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toString())) {
261             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
262             // TestFileListener manipulates status
263             monitor.setDelay(DELAY_MILLIS);
264             monitor.addFile(fileObject);
265             monitor.start();
266             try {
267                 testFile.delete();
268                 waitFor(Status.DELETED, DELAY_MILLIS * 5);
269             } finally {
270                 monitor.stop();
271             }
272         }
273     }
274 
275     @Test
276     public void testFileModified() throws Exception {
277         writeToFile(testFile);
278         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toURL().toString())) {
279             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
280             // TestFileListener manipulates status
281             monitor.setDelay(DELAY_MILLIS);
282             monitor.addFile(fileObject);
283             monitor.start();
284             try {
285                 // Need a long delay to insure the new timestamp doesn't truncate to be the same as
286                 // the current timestammp. Java only guarantees the timestamp will be to 1 second.
287                 Thread.sleep(DELAY_MILLIS * 10);
288                 final long valueMillis = System.currentTimeMillis();
289                 final boolean rcMillis = testFile.setLastModified(valueMillis);
290                 assertTrue("setLastModified succeeded", rcMillis);
291                 waitFor(Status.CHANGED, DELAY_MILLIS * 5);
292             } finally {
293                 monitor.stop();
294             }
295         }
296     }
297 
298     @Test
299     public void testFileMonitorRestarted() throws Exception {
300         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI().toString())) {
301             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
302             // TestFileListener manipulates status
303             monitor.setDelay(DELAY_MILLIS);
304             monitor.addFile(fileObject);
305 
306             monitor.start();
307             try {
308                 writeToFile(testFile);
309                 Thread.sleep(DELAY_MILLIS * 5);
310             } finally {
311                 monitor.stop();
312             }
313 
314             monitor.start();
315             try {
316                 testFile.delete();
317                 waitFor(Status.DELETED, DELAY_MILLIS * 5);
318             } finally {
319                 monitor.stop();
320             }
321         }
322     }
323 
324     @Test
325     public void testFileRecreated() throws Exception {
326         try (final FileObject fileObject = fileSystemManager.resolveFile(testFile.toURI())) {
327             final DefaultFileMonitor monitor = new DefaultFileMonitor(new TestFileListener());
328             // TestFileListener manipulates status
329             monitor.setDelay(DELAY_MILLIS);
330             monitor.addFile(fileObject);
331             monitor.start();
332             try {
333                 writeToFile(testFile);
334                 waitFor(Status.CREATED, DELAY_MILLIS * 10);
335                 status = null;
336                 testFile.delete();
337                 waitFor(Status.DELETED, DELAY_MILLIS * 10);
338                 status = null;
339                 Thread.sleep(DELAY_MILLIS * 5);
340                 monitor.addFile(fileObject);
341                 writeToFile(testFile);
342                 waitFor(Status.CREATED, DELAY_MILLIS * 10);
343             } finally {
344                 monitor.stop();
345             }
346         }
347     }
348 
349     private void waitFor(final Status expected, final long timeoutMillis) throws InterruptedException {
350         if (expected == status) {
351             return;
352         }
353         long remaining = timeoutMillis;
354         final long interval = timeoutMillis / 10;
355         while (remaining > 0) {
356             Thread.sleep(interval);
357             remaining -= interval;
358             if (expected == status) {
359                 return;
360             }
361         }
362         assertTrue("No event occurred", status != null);
363         assertEquals("Incorrect event " + status, expected, status);
364     }
365 
366     private void writeToFile(final File file) throws IOException {
367         // assertTrue(file.delete());
368         try (final BufferedWriter out = Files.newBufferedWriter(file.toPath())) {
369             out.write("string=value1");
370         }
371     }
372 
373 }