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