Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 3a9004f

Browse files
committed
Updated ReamE and refacotr of file watcher
1 parent 3c932b0 commit 3a9004f

File tree

10 files changed

+523
-251
lines changed

10 files changed

+523
-251
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,20 @@ During the time of ApplicationContext start also a new instance of Instantiation
2727
Google Guava is used to implement a simple Publish & Subscribe (Pub-Sub) Pattern so that beans can be updated once created, i.e. a bean can subscribe to events. (see: EventBus)
2828
EventBus was chosen as it is a very easy and simplistic way to implement loosely couple object structure. (see: blog)
2929

30-
When each properties file resource is loaded a FileWatch.class (see:FaileWatch.class) is started and attached to the given resource, reporting on any java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY events from the host operating system
30+
When each properties file resource is loaded a PropertiesWatcher.class (see:FaileWatch.class) is started and attached to the given resource set, reporting on any java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY events from the host operating system
3131

3232
When an ENTRY_MODIFY event is fired firstly the resource changed is checked for property value changes then any bean subscribing to changes to the modified property has the specified field value updated with the new property
3333

34+
Each resource specified starts a new thread per parent directory i.e. two properties files in the same directory requires only one ResourceWatcher thread, three properties files in three different directories will start three threads.
35+
3436
### Tests ###
3537
A set of integration and unit tests can be found in _src/test/java_ (tests) & _src/test/resources_ (test resources)
3638

3739
### TODO (Unfinished) ###
3840
* Update test method names
3941
* Creation of any test utilities or helper classes
40-
* Assign a new thread to each resource directory specified (see: FileWatcherUnitTest.class for failing test @Ignore)
42+
* Add test for modifying file in directory which is not a properties file
43+
* Replace callback EventHandler with Guava EventBus
4144

4245
### Why? ###
4346
* Useful for web applications which often need configuration changes but you don't always want to restart the application before new properties are used.
@@ -51,7 +54,8 @@ A set of integration and unit tests can be found in _src/test/java_ (tests) & _s
5154
### Future Changes ###
5255
* Ability to use Spring Expression language to map properties files
5356
* Support for Java 7 Data and Time classes
54-
* Add properties source of Database not just properties files
57+
* Include the ability to define a database driven properties source not just properties files
58+
* If one resource thread dies at present all watching threads are killed, graceful handle a thread being killed.
5559

5660
### Supported Property Type Conversions Available ###
5761
* Joda Time Library (2.1) - [link](http://joda-time.sourceforge.net/)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package com.morgan.design.properties.internal;
2+
3+
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
4+
5+
import java.io.IOException;
6+
import java.nio.file.FileSystems;
7+
import java.nio.file.Path;
8+
import java.nio.file.Paths;
9+
import java.nio.file.WatchEvent;
10+
import java.nio.file.WatchEvent.Kind;
11+
import java.nio.file.WatchKey;
12+
import java.nio.file.WatchService;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
import org.springframework.core.io.Resource;
20+
21+
import com.google.common.collect.Lists;
22+
import com.google.common.collect.Maps;
23+
24+
public class CopyOfPropertiesWatcher implements Runnable {
25+
26+
protected static Logger log = LoggerFactory.getLogger(CopyOfPropertiesWatcher.class);
27+
28+
public interface EventPublisher {
29+
void onResourceChanged(Resource resource);
30+
}
31+
32+
private final Resource[] locations;
33+
private final EventPublisher eventPublisher;
34+
35+
private boolean exit;
36+
private WatchService watchService;
37+
private List<Thread> runningWatchers;
38+
39+
public CopyOfPropertiesWatcher(final Resource[] locations, final EventPublisher eventPublisher) throws IOException {
40+
this.locations = locations;
41+
this.eventPublisher = eventPublisher;
42+
this.exit = false;
43+
this.runningWatchers = Lists.newArrayList();
44+
this.watchService = FileSystems.getDefault()
45+
.newWatchService();
46+
}
47+
48+
@Override
49+
public void run() {
50+
final Map<Path, List<Resource>> pathsAndResources = findAvailableResourcePaths();
51+
52+
for (final Path pathToWatch : pathsAndResources.keySet()) {
53+
final List<Resource> availableResources = pathsAndResources.get(pathToWatch);
54+
final Thread thread = new Thread(new ResourceWatcher(pathToWatch, availableResources));
55+
this.runningWatchers.add(thread);
56+
thread.start();
57+
log.debug("Starting ResourceWatcher on file {}", availableResources);
58+
}
59+
}
60+
61+
private Map<Path, List<Resource>> findAvailableResourcePaths() {
62+
final Map<Path, List<Resource>> map = Maps.newHashMap();
63+
for (final Resource resource : this.locations) {
64+
final Path resourceParentPath = getResourceParentPath(resource);
65+
if (null == map.get(resourceParentPath)) {
66+
map.put(resourceParentPath, new ArrayList<Resource>());
67+
}
68+
map.get(resourceParentPath)
69+
.add(resource);
70+
}
71+
return map;
72+
}
73+
74+
private Path getResourceParentPath(final Resource resource) {
75+
try {
76+
return Paths.get(resource.getFile()
77+
.getParentFile()
78+
.toURI());
79+
}
80+
catch (final IOException e) {
81+
log.error("Unable to get resource path", e);
82+
}
83+
return null;
84+
}
85+
86+
public void stop() {
87+
try {
88+
log.debug("Stopping file watcher");
89+
this.watchService.close();
90+
}
91+
catch (final IOException e) {
92+
log.error("Unable to stop file watcher", e);
93+
}
94+
this.exit = true;
95+
}
96+
97+
public boolean isRunning() {
98+
return this.exit == false;
99+
}
100+
101+
private boolean doNotExit() {
102+
return !this.exit;
103+
}
104+
105+
private void publishResourceChangedEvent(final Resource resource) {
106+
this.eventPublisher.onResourceChanged(resource);
107+
}
108+
109+
private WatchService getWatchService() {
110+
return this.watchService;
111+
}
112+
113+
private class ResourceWatcher implements Runnable {
114+
115+
private final Path path;
116+
private final List<Resource> resources;
117+
118+
public ResourceWatcher(final Path path, final List<Resource> resources) {
119+
this.path = path;
120+
this.resources = resources;
121+
}
122+
123+
@Override
124+
public void run() {
125+
try {
126+
while (doNotExit()) {
127+
log.debug("Watching for modifcation events from path {}", this.path.toString());
128+
final WatchKey basePathWatchKey = this.path.register(getWatchService(), ENTRY_MODIFY);
129+
130+
try {
131+
final WatchKey watchKey = getWatchService().take(); // Await modification
132+
133+
for (final WatchEvent<?> event : basePathWatchKey.pollEvents()) {
134+
final Path watchedPath = (Path) watchKey.watchable();
135+
final Kind<?> eventKind = event.kind();// returns the event type
136+
final Path target = (Path) event.context();// returns the context of the event
137+
138+
log.debug("File modification Event Triggered");
139+
if (isValidTargetFile(target)) {
140+
log.debug("Resource Change is a known properties file {}", target.getFileName()
141+
.toString());
142+
log.debug("Target [{}]", target);
143+
log.debug("Event Kind [{}]", eventKind);
144+
log.debug("Watched Path [{}]", watchedPath);
145+
publishResourceChangedEvent(getResource(target));
146+
}
147+
}
148+
final boolean valid = null != watchKey && watchKey.reset();
149+
if (!valid) {
150+
stop();
151+
return;
152+
}
153+
}
154+
catch (final InterruptedException e) {
155+
e.printStackTrace();
156+
Thread.currentThread()
157+
.interrupt();
158+
}
159+
}
160+
}
161+
catch (final Exception e) {
162+
log.error("Exception thrown when watching resources, fileName: TODO", e);
163+
stop();
164+
}
165+
}
166+
167+
private boolean isValidTargetFile(final Path target) {
168+
for (final Resource resource : this.resources) {
169+
if (pathMatchesResource(target, resource)) {
170+
return true;
171+
}
172+
}
173+
return false;
174+
}
175+
176+
public Resource getResource(final Path target) {
177+
for (final Resource resource : this.resources) {
178+
if (pathMatchesResource(target, resource)) {
179+
return resource;
180+
}
181+
}
182+
return null;
183+
}
184+
185+
private boolean pathMatchesResource(final Path target, final Resource resource) {
186+
return target.getFileName()
187+
.toString()
188+
.equals(resource.getFilename());
189+
}
190+
}
191+
192+
}

src/main/java/com/morgan/design/properties/internal/FileWatcher.java

Lines changed: 0 additions & 112 deletions
This file was deleted.

0 commit comments

Comments
 (0)