35
35
import java .util .concurrent .ConcurrentHashMap ;
36
36
import java .util .concurrent .CopyOnWriteArrayList ;
37
37
import java .util .concurrent .TimeUnit ;
38
- import java .util .stream .Collectors ;
39
38
40
39
import org .apache .commons .logging .Log ;
41
40
import org .apache .commons .logging .LogFactory ;
@@ -86,26 +85,18 @@ void watch(Set<Path> paths, Runnable action) {
86
85
this .thread = new WatcherThread ();
87
86
this .thread .start ();
88
87
}
89
- Set <Path > actualPaths = new HashSet <>();
88
+ Set <Path > registrationPaths = new HashSet <>();
90
89
for (Path path : paths ) {
91
- actualPaths . add ( resolveSymlinkIfNecessary (path ));
90
+ registrationPaths . addAll ( getRegistrationPaths (path ));
92
91
}
93
- this .thread .register (new Registration (actualPaths , action ));
92
+ this .thread .register (new Registration (registrationPaths , action ));
94
93
}
95
94
catch (IOException ex ) {
96
95
throw new UncheckedIOException ("Failed to register paths for watching: " + paths , ex );
97
96
}
98
97
}
99
98
}
100
99
101
- private static Path resolveSymlinkIfNecessary (Path path ) throws IOException {
102
- if (Files .isSymbolicLink (path )) {
103
- Path target = path .resolveSibling (Files .readSymbolicLink (path ));
104
- return resolveSymlinkIfNecessary (target );
105
- }
106
- return path ;
107
- }
108
-
109
100
@ Override
110
101
public void close () throws IOException {
111
102
synchronized (this .lock ) {
@@ -123,6 +114,44 @@ public void close() throws IOException {
123
114
}
124
115
}
125
116
117
+ /**
118
+ * Retrieves all {@link Path Paths} that should be registered for the specified
119
+ * {@link Path}. If the path is a symlink, changes to the symlink should be monitored,
120
+ * not just the file it points to. For example, for the given {@code keystore.jks}
121
+ * path in the following directory structure:<pre>
122
+ * .
123
+ * ├── ..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f
124
+ * │ ├── keystore.jks
125
+ * ├── ..data -> ..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f
126
+ * ├── keystore.jks -> ..data/keystore.jks
127
+ * </pre> the resulting paths would include:
128
+ * <ul>
129
+ * <li><b>keystore.jks</b></li>
130
+ * <li><b>..data/keystore.jks</b></li>
131
+ * <li><b>..data</b></li>
132
+ * <li><b>..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f/keystore.jks</b></li>
133
+ * </ul>
134
+ * @param path the path
135
+ * @return all possible {@link Path} instances to be registered
136
+ * @throws IOException if an I/O error occurs
137
+ */
138
+ private static Set <Path > getRegistrationPaths (Path path ) throws IOException {
139
+ path = path .toAbsolutePath ();
140
+ Set <Path > result = new HashSet <>();
141
+ result .add (path );
142
+ Path parent = path .getParent ();
143
+ if (parent != null && Files .isSymbolicLink (parent )) {
144
+ result .add (parent );
145
+ Path target = parent .resolveSibling (Files .readSymbolicLink (parent ));
146
+ result .addAll (getRegistrationPaths (target .resolve (path .getFileName ())));
147
+ }
148
+ else if (Files .isSymbolicLink (path )) {
149
+ Path target = path .resolveSibling (Files .readSymbolicLink (path ));
150
+ result .addAll (getRegistrationPaths (target ));
151
+ }
152
+ return result ;
153
+ }
154
+
126
155
/**
127
156
* The watcher thread used to check for changes.
128
157
*/
@@ -145,11 +174,15 @@ private void onThreadException(Thread thread, Throwable throwable) {
145
174
}
146
175
147
176
void register (Registration registration ) throws IOException {
177
+ Set <Path > directories = new HashSet <>();
148
178
for (Path path : registration .paths ()) {
149
179
if (!Files .isRegularFile (path ) && !Files .isDirectory (path )) {
150
180
throw new IOException ("'%s' is neither a file nor a directory" .formatted (path ));
151
181
}
152
182
Path directory = Files .isDirectory (path ) ? path : path .getParent ();
183
+ directories .add (directory );
184
+ }
185
+ for (Path directory : directories ) {
153
186
WatchKey watchKey = register (directory );
154
187
this .registrations .computeIfAbsent (watchKey , (key ) -> new CopyOnWriteArrayList <>()).add (registration );
155
188
}
@@ -224,10 +257,6 @@ public void close() throws IOException {
224
257
*/
225
258
private record Registration (Set <Path > paths , Runnable action ) {
226
259
227
- Registration {
228
- paths = paths .stream ().map (Path ::toAbsolutePath ).collect (Collectors .toSet ());
229
- }
230
-
231
260
boolean manages (Path file ) {
232
261
Path absolutePath = file .toAbsolutePath ();
233
262
return this .paths .contains (absolutePath ) || isInDirectories (absolutePath );
0 commit comments