1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.resourceloader.file;
35
36 import static info.magnolia.resourceloader.ResourceMatcher.file;
37 import static info.magnolia.resourceloader.ResourceOriginChange.Type.*;
38 import static info.magnolia.resourceloader.ResourceOriginChangeMatcher.resourceChange;
39 import static info.magnolia.resourceloader.file.FileSystemResourceOrigin.RESOURCES_DIR_PROPERTY;
40 import static info.magnolia.test.hamcrest.ExceptionMatcher.instanceOf;
41 import static info.magnolia.test.hamcrest.ExecutionMatcher.throwsAnException;
42 import static java.nio.file.Files.exists;
43 import static org.hamcrest.Matchers.is;
44 import static org.junit.Assert.assertThat;
45 import static org.mockito.Mockito.*;
46
47 import info.magnolia.dirwatch.DirectoryWatcher;
48 import info.magnolia.dirwatch.DirectoryWatcherService;
49 import info.magnolia.init.MagnoliaConfigurationProperties;
50 import info.magnolia.resourceloader.ResourceChangeHandler;
51 import info.magnolia.resourceloader.ResourceOrigin;
52
53 import java.io.File;
54 import java.io.FileOutputStream;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.nio.file.StandardCopyOption;
59
60 import org.apache.commons.io.FileUtils;
61 import org.apache.commons.io.IOUtils;
62 import org.junit.Before;
63 import org.junit.Rule;
64 import org.junit.Test;
65 import org.junit.rules.TemporaryFolder;
66
67 import com.google.common.base.Predicate;
68
69 public class FileWatcherCallbackTest {
70
71 private static final int FILE_OPERATION_TIMEOUT = 30 * 1000;
72 private static final String IGNORED_DIRECTORY = "rsyncstagingdir";
73
74 @Rule
75 public TemporaryFolder watchedDir = new TemporaryFolder();
76
77 @Rule
78 public TemporaryFolder nonWatchedDir = new TemporaryFolder();
79
80 private Predicate<Path> watchedPathFilter = path -> !path.endsWith(IGNORED_DIRECTORY);
81
82 private FileSystemResourceOrigin fileSystemResourceOrigin;
83 private DirectoryWatcher watcher;
84 private FileWatcherCallback fileWatcherCallback;
85
86 @Before
87 public void setUp() throws Exception {
88 final MagnoliaConfigurationProperties mcp = mock(MagnoliaConfigurationProperties.class);
89 doReturn(watchedDir.getRoot().toString()).when(mcp).getProperty(RESOURCES_DIR_PROPERTY);
90
91 watcher = new DirectoryWatcher(true, true, mcp);
92 this.fileSystemResourceOrigin = new FileSystemResourceOrigin(mcp, mock(DirectoryWatcherService.class), "foo");
93
94 fileWatcherCallback = new FileWatcherCallback(fileSystemResourceOrigin, watchedPathFilter);
95 watcher.register(watchedDir.getRoot().toPath(), watchedPathFilter, fileWatcherCallback);
96
97 final Thread watcherThread = new Thread(watcher);
98 watcherThread.start();
99 }
100
101 @Test
102 public void communicatesFolderCreationAndDeletion() throws Exception {
103
104
105 final ResourceChangeHandler handler = mock(ResourceChangeHandler.class);
106 fileSystemResourceOrigin.registerResourceChangeHandler(handler);
107
108
109
110 final File qux = watchedDir.newFolder("qux");
111 final Path quux = Files.createFile(qux.toPath().resolve("quux"));
112 final Path xyzzy = Files.createFile(qux.toPath().resolve("xyzzy"));
113
114
115
116 assertThat(quux.toFile().exists(), is(true));
117 assertThat(xyzzy.toFile().exists(), is(true));
118
119
120 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/qux")));
121
122
123
124 verify(handler, timeout(FILE_OPERATION_TIMEOUT).atLeast(1)).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/qux/quux")));
125 verify(handler, timeout(FILE_OPERATION_TIMEOUT).atLeast(1)).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/qux/xyzzy")));
126
127
128
129 FileUtils.deleteDirectory(qux);
130
131
132
133
134 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(REMOVED).at("/qux")));
135 }
136
137 @Test
138 public void communicatesAllPossibleFileChangeEvents() throws Exception {
139
140
141 final ResourceChangeHandler handler = mock(ResourceChangeHandler.class);
142 fileSystemResourceOrigin.registerResourceChangeHandler(handler);
143
144
145 File foo = watchedDir.newFile("foo");
146
147
148 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/foo")));
149
150
151
152 final FileOutputStream fos = new FileOutputStream(foo);
153 fos.write(1);
154 IOUtils.closeQuietly(fos);
155
156
157 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(MODIFIED).at("/foo")));
158
159
160
161 foo.delete();
162
163
164 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(REMOVED).at("/foo")));
165 }
166
167 @Test
168 public void movedDirectoryIsDetected() throws Exception {
169
170 final ResourceChangeHandler resourceChangeHandler = mock(ResourceChangeHandler.class);
171 fileSystemResourceOrigin.registerResourceChangeHandler(resourceChangeHandler);
172
173
174
175 final Path foo = watchedDir.newFolder("foo").toPath();
176 Files.createFile(foo.resolve("bar.yaml"));
177
178
179
180
181
182 verify(resourceChangeHandler, timeout(FILE_OPERATION_TIMEOUT).atLeast(1)).onResourceChanged(argThat(resourceChange().at("/foo/bar.yaml").ofType(ADDED)));
183
184
185 final Path bar = Files.createDirectory(watchedDir.getRoot().toPath().resolve("bar"));
186
187 assertThat(() -> fileSystemResourceOrigin.getByPath("/bar/bar.yaml"),
188 throwsAnException(instanceOf(ResourceOrigin.ResourceNotFoundException.class)));
189
190
191 Files.move(foo, bar, StandardCopyOption.REPLACE_EXISTING);
192
193
194 verify(resourceChangeHandler, timeout(FILE_OPERATION_TIMEOUT).atLeastOnce()).onResourceChanged(argThat(resourceChange().at("/foo").ofType(REMOVED)));
195
196 verify(resourceChangeHandler, timeout(FILE_OPERATION_TIMEOUT).atLeastOnce()).onResourceChanged(argThat(resourceChange().at("/bar/bar.yaml").ofType(ADDED)));
197
198 assertThat(fileSystemResourceOrigin.getByPath("/bar/bar.yaml"), file().withPath("/bar/bar.yaml"));
199 }
200
201 @Test
202 public void subTreeIsSkippedWhenFileCantBeRead() throws Exception {
203
204 final ResourceChangeHandler handler = mock(ResourceChangeHandler.class);
205 fileSystemResourceOrigin.registerResourceChangeHandler(handler);
206
207
208
209 File toCreateDir = watchedDir.newFolder("qux");
210 Files.createFile(toCreateDir.toPath().resolve("bar.yaml"));
211 assertThat(fileSystemResourceOrigin.getByPath("/qux/bar.yaml"), file().withPath("/qux/bar.yaml"));
212
213
214
215 verify(handler, timeout(FILE_OPERATION_TIMEOUT)).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/qux")));
216
217
218
219 toCreateDir.setReadable(false);
220 toCreateDir.setWritable(false);
221 toCreateDir.setExecutable(false);
222
223
224
225 assertThat(() -> fileSystemResourceOrigin.getByPath("/qux/bar.yaml"), throwsAnException(instanceOf(ResourceOrigin.ResourceNotFoundException.class)));
226 }
227
228 @Test
229 public void watchedPathFilterDiscardsIgnoredElementsRegistration() throws Exception {
230
231 final ResourceChangeHandler handler = mock(ResourceChangeHandler.class);
232 fileSystemResourceOrigin.registerResourceChangeHandler(handler);
233
234
235
236 final File qux = watchedDir.newFolder(IGNORED_DIRECTORY);
237 final Path quux = Files.createFile(qux.toPath().resolve("quux"));
238 final Path xyzzy = Files.createFile(qux.toPath().resolve("xyzzy"));
239 assertThat(quux.toFile().exists(), is(true));
240 assertThat(xyzzy.toFile().exists(), is(true));
241
242
243
244 verify(handler, never()).onResourceChanged(argThat(resourceChange().ofType(ADDED).at(getIgnoredDirectoryPath(""))));
245 verify(handler, never()).onResourceChanged(argThat(resourceChange().ofType(ADDED).at(getIgnoredDirectoryPath("/quux"))));
246 verify(handler, never()).onResourceChanged(argThat(resourceChange().ofType(ADDED).at(getIgnoredDirectoryPath("/xyzzy"))));
247 }
248
249 @Test
250 public void allowsDirectoryWatchingViaSymbolicLinks() throws Exception {
251
252 final ResourceChangeHandler handler = mock(ResourceChangeHandler.class);
253 fileSystemResourceOrigin.registerResourceChangeHandler(handler);
254
255
256 final Path symLinkPath = watchedDir.getRoot().toPath().resolve(Paths.get("symLink"));
257 final Path symLinkTarget = nonWatchedDir.newFolder("symLinkTarget").toPath();
258 Files.createFile(symLinkTarget.resolve(Paths.get("bar.yaml")));
259 final Path symbolicLink = Files.createSymbolicLink(symLinkPath, symLinkTarget);
260
261
262 final Path barPathViaSymLink = symbolicLink.resolve(Paths.get("bar.yaml"));
263 assertThat(exists(barPathViaSymLink), is(true));
264
265
266
267 watcher.register(symbolicLink, watchedPathFilter, fileWatcherCallback);
268
269
270
271 verify(handler, timeout(FILE_OPERATION_TIMEOUT).atLeastOnce()).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/symLink/bar.yaml")));
272
273
274
275 Files.createFile(symLinkTarget.resolve(Paths.get("foo.yaml")));
276
277
278 final Path fooPathViaSymLink = symbolicLink.resolve(Paths.get("foo.yaml"));
279 assertThat(exists(fooPathViaSymLink), is(true));
280
281
282
283 verify(handler, timeout(FILE_OPERATION_TIMEOUT).atLeastOnce()).onResourceChanged(argThat(resourceChange().ofType(ADDED).at("/symLink/foo.yaml")));
284 }
285
286 private String getIgnoredDirectoryPath(String path) {
287 Path ignoredDirectoryPath = Paths.get(IGNORED_DIRECTORY);
288 return ignoredDirectoryPath.resolve(path).toString();
289 }
290 }