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.dirwatch;
35
36 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
37 import static java.nio.file.StandardWatchEventKinds.*;
38
39 import info.magnolia.cms.util.ExceptionUtil;
40
41 import java.io.IOException;
42 import java.nio.file.FileSystems;
43 import java.nio.file.FileVisitResult;
44 import java.nio.file.Files;
45 import java.nio.file.LinkOption;
46 import java.nio.file.Path;
47 import java.nio.file.SimpleFileVisitor;
48 import java.nio.file.WatchEvent;
49 import java.nio.file.WatchKey;
50 import java.nio.file.WatchService;
51 import java.nio.file.attribute.BasicFileAttributes;
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import com.google.common.base.Predicate;
61
62
63
64
65
66
67
68
69
70
71 public class DirectoryWatcher implements Runnable {
72
73 private static final Logger log = LoggerFactory.getLogger(DirectoryWatcher.class);
74
75 private final WatchService watcher;
76 private final Map<WatchKey, Path> keys = new HashMap<>();
77
78 private final boolean recursive;
79 private final LinkOption[] visitorOptions;
80 private final List<WatcherRegistration> registrations = new ArrayList<>();
81
82 public DirectoryWatcher(boolean recursive, boolean followLinks) throws IOException {
83 this.recursive = recursive;
84 this.visitorOptions = followLinks ? new LinkOption[] { NOFOLLOW_LINKS } : new LinkOption[0];
85 this.watcher = FileSystems.getDefault().newWatchService();
86 }
87
88 public void register(Path dir, Predicate<Path> filterPredicate, WatcherCallback callback) throws IOException {
89 if (recursive) {
90 log.debug("Scanning {}", dir);
91 registerRecursively(dir, filterPredicate);
92 log.debug("Done scanning {}", dir);
93 } else {
94 registerDirectory(dir);
95 }
96 registrations.add(new WatcherRegistration(dir, filterPredicate, callback));
97 }
98
99
100
101
102 protected void registerDirectory(Path dir) throws IOException {
103 final WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
104
105
106
107
108 if (log.isDebugEnabled()) {
109 Path prev = keys.get(key);
110 if (prev == null) {
111 log.debug("* register: {}", dir);
112 } else {
113 log.debug("* update: {} -> {}", prev, dir);
114 }
115 }
116 keys.put(key, dir);
117 }
118
119
120
121
122 protected void registerRecursively(final Path start, final Predicate<Path> filterPredicate) throws IOException {
123
124
125 Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
126
127 @Override
128 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
129 if (!filterPredicate.apply(dir)) {
130 return FileVisitResult.SKIP_SUBTREE;
131 }
132
133 registerDirectory(dir);
134 return FileVisitResult.CONTINUE;
135 }
136 });
137 }
138
139 @Override
140 public void run() {
141 try {
142 while (!Thread.currentThread().isInterrupted()) {
143
144
145 final WatchKey key;
146 try {
147 key = watcher.take();
148 } catch (InterruptedException x) {
149 Thread.currentThread().interrupt();
150 return;
151 }
152
153 final Path dir = keys.get(key);
154 if (dir == null) {
155 log.debug("WatchKey not recognized!!");
156 continue;
157 }
158
159
160 final List<WatchEvent<?>> watchEvents = key.pollEvents();
161 log.debug(" {} events", watchEvents.size());
162
163 for (WatchEvent<?> event : watchEvents) {
164 final WatchEvent.Kind<?> kind = event.kind();
165 log.debug("event: {}", event);
166 log.debug("kind: {}", kind);
167
168
169 if (kind == OVERFLOW) {
170 continue;
171 }
172
173 final Path completePath = getCompletePath(key, event);
174 log.debug("--> {} : {}: {}", Thread.currentThread(), event.kind().name(), completePath);
175
176
177 if (Files.isDirectory(completePath, visitorOptions)) {
178 if (recursive && (kind == ENTRY_CREATE)) {
179
180
181 try {
182 for (WatcherRegistration registration : registrations) {
183 if (!completePath.startsWith(registration.getRootPath())) {
184 continue;
185 }
186 Predicate<Path> filterPredicate = registration.getFilterPredicate();
187 registerRecursively(completePath, filterPredicate);
188 }
189 } catch (IOException x) {
190 log.warn("Unable to register all subdirectories because of the following exception:", x);
191 }
192 }
193
194 log.debug(" --> watch keys {}", keys.values());
195 }
196
197
198 try {
199 processEvent(kind, completePath, event);
200 } catch (Throwable t) {
201 log.error("Exception when executing callback for {}: {}", event.context(), ExceptionUtil.exceptionToWords(t), t);
202
203 }
204 }
205
206
207 boolean valid = key.reset();
208 if (!valid) {
209 keys.remove(key);
210
211
212 if (keys.isEmpty()) {
213 break;
214 }
215 }
216 }
217
218 } catch (Throwable t) {
219 log.error("Exception occurred in DirectoryWatcher: {}", t, t);
220 throw t;
221 }
222 }
223
224
225
226
227
228
229
230
231 protected void processEvent(final WatchEvent.Kind<?> kind, final Path completePath, final WatchEvent<?> watchEvent) {
232 if (kind != ENTRY_CREATE && kind != ENTRY_MODIFY && kind != ENTRY_DELETE) {
233 throw new RuntimeException("Unknown event type " + kind + " for " + completePath.toAbsolutePath().toString());
234 }
235
236 for (WatcherRegistration registration : registrations) {
237 if (!completePath.startsWith(registration.getRootPath())) {
238 continue;
239 }
240
241 WatcherCallback callback = registration.getCallback();
242 if (kind == ENTRY_CREATE) {
243 callback.added(completePath);
244 } else if (kind == ENTRY_DELETE) {
245 callback.removed(completePath);
246 } else if (kind == ENTRY_MODIFY) {
247 callback.modified(completePath);
248 }
249 }
250 }
251
252
253
254
255 private Path getCompletePath(WatchKey key, WatchEvent<?> watchEvent) {
256
257 final Path path = cast(watchEvent).context();
258 final Path parent = (Path) key.watchable();
259 return parent.resolve(path);
260 }
261
262 @SuppressWarnings("unchecked")
263 private WatchEvent<Path> cast(WatchEvent<?> event) {
264 return (WatchEvent<Path>) event;
265 }
266
267 private static class WatcherRegistration {
268
269 private final Path rootPath;
270 private final Predicate<Path> filterPredicate;
271 private final WatcherCallback callback;
272
273 public WatcherRegistration(Path rootPath, Predicate<Path> filterPredicate, WatcherCallback callback) {
274 this.rootPath = rootPath;
275 this.filterPredicate = filterPredicate;
276 this.callback = callback;
277 }
278
279 public Path getRootPath() {
280 return rootPath;
281 }
282
283 public Predicate<Path> getFilterPredicate() {
284 return filterPredicate;
285 }
286
287 public WatcherCallback getCallback() {
288 return callback;
289 }
290 }
291 }