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.classpathwatch;
35
36 import java.io.IOException;
37 import java.net.URL;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.Set;
42
43 import org.reflections.Reflections;
44 import org.reflections.scanners.ResourcesScanner;
45 import org.reflections.util.ClasspathHelper;
46 import org.reflections.util.ConfigurationBuilder;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import com.google.common.base.Function;
51 import com.google.common.base.Predicate;
52 import com.google.common.base.Predicates;
53 import com.google.common.collect.Collections2;
54
55
56
57
58 public class ClasspathScanner implements Runnable {
59
60 private static final Logger log = LoggerFactory.getLogger(ClasspathScanner.class);
61
62 private final Predicate<String> resourcesFilter;
63 private final Predicate<? super URL> urlsFilter;
64
65 private long lastScan;
66
67 private List<Function<String, Void>> callbacks = new ArrayList<>();
68
69
70
71
72
73
74
75 ClasspathScanner(Predicate<String> resourcesFilter, Predicate<? super URL> urlsFilter) {
76 this.resourcesFilter = resourcesFilter;
77 this.urlsFilter = urlsFilter;
78 lastScan = System.currentTimeMillis();
79 }
80
81 @Override
82 public void run() {
83
84 final long start = System.currentTimeMillis();
85
86 final Collection<URL> classpathUrls = classpathUrls();
87
88
89
90
91 final Reflections reflections = new Reflections(new ConfigurationBuilder()
92 .setScanners(new ResourcesScanner())
93 .setUrls(classpathUrls)
94 .filterInputsBy(resourcesFilter)
95 );
96
97 final Set<String> resources = reflections.getResources(Predicates.<String>alwaysTrue());
98 long scanDone = System.currentTimeMillis();
99 log.debug("Took {}ms to find {} resources", scanDone - start, resources.size());
100
101
102 for (String resource : resources) {
103 URL resourceUrl = getUrl(resource);
104
105 long lastModified = getLastModified(resourceUrl);
106
107 if (lastModified > lastScan) {
108 log.debug("Found resource change at {}, triggering callback.", resource);
109 for (Function<String, Void> callback : callbacks) {
110 try {
111 callback.apply(resource);
112 } catch (Throwable t) {
113 log.error("Caught exception while invoking callback:", t);
114 }
115 }
116
117 }
118 }
119 log.debug("Took {}ms to check for recent changes", System.currentTimeMillis() - scanDone);
120 lastScan = System.currentTimeMillis();
121 }
122
123 public void watchResourceChanges(Function<String, Void> callbackFunction) {
124 callbacks.add(callbackFunction);
125 }
126
127 protected Collection<URL> classpathUrls() {
128 final Collection<URL> allURLs = ClasspathHelper.forClassLoader(ClasspathHelper.contextClassLoader());
129 return Collections2.filter(allURLs, urlsFilter);
130 }
131
132 protected URL getUrl(String resourcePath) {
133 final URL url = ClasspathHelper.contextClassLoader().getResource(resourcePath);
134 if (url == null) {
135 throw new IllegalStateException("Can't find resource at " + resourcePath);
136 }
137 return url;
138 }
139
140 protected long getLastModified(URL resourceUrl) {
141 try {
142 return resourceUrl.openConnection().getLastModified();
143 } catch (IOException e) {
144 throw new RuntimeException("Last modified time could not be retrieved for path " + resourceUrl.toExternalForm() + " : " + e, e);
145 }
146 }
147
148 }