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.classpath.service.impl.devmode;
35
36 import static com.google.common.collect.Sets.*;
37 import static info.magnolia.resourceloader.ResourceOriginChange.Type.*;
38
39 import info.magnolia.resourceloader.ResourceOriginChange;
40 import info.magnolia.resourceloader.classpath.DefaultClasspathServiceConfigurations;
41 import info.magnolia.resourceloader.classpath.hierarchy.ClasspathEntry;
42 import info.magnolia.resourceloader.classpath.service.ClasspathServiceConfiguration;
43 import info.magnolia.resourceloader.classpath.service.MonitoredClasspathService;
44 import info.magnolia.resourceloader.classpath.service.impl.URLConnectionUtil;
45 import info.magnolia.resourceloader.classpath.service.impl.base.ClasspathEntriesResolver;
46 import info.magnolia.resourceloader.classpath.service.impl.base.ClasspathServiceBase;
47
48 import java.io.IOException;
49 import java.net.URL;
50 import java.net.URLConnection;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.concurrent.Executors;
56 import java.util.concurrent.ScheduledExecutorService;
57 import java.util.concurrent.TimeUnit;
58 import java.util.regex.Pattern;
59
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.common.base.Optional;
64 import com.google.common.base.Predicate;
65 import com.google.common.collect.Iterables;
66 import com.google.common.collect.Lists;
67 import com.google.common.collect.Maps;
68
69 import lombok.Value;
70
71
72
73
74
75
76
77
78
79 public class DevelopmentModeClasspathService extends ClasspathServiceBase implements MonitoredClasspathService {
80
81 private static final Logger log = LoggerFactory.getLogger(DevelopmentModeClasspathService.class);
82
83 private static final int SCAN_INTERVAL = 10;
84
85 private final List<ClasspathResourceCallback> resourceModificationCallbacks = Collections.synchronizedList(Lists.<ClasspathResourceCallback>newArrayList());
86
87 private ScheduledExecutorService monitor;
88
89 public DevelopmentModeClasspathService(final DefaultClasspathServiceConfigurations configurations, ClasspathEntriesResolver classpathEntriesResolver) {
90 this(configurations.developmentMode().build(), classpathEntriesResolver);
91 }
92
93
94
95
96 public DevelopmentModeClasspathService(final ClasspathServiceConfiguration configuration, ClasspathEntriesResolver classpathEntriesResolver) {
97 super(configuration, classpathEntriesResolver);
98 }
99
100 @Override
101 public void registerResourceChangeCallback(ClasspathResourceCallback resourceChangeCallback) {
102 resourceModificationCallbacks.add(resourceChangeCallback);
103 }
104
105 @Override
106 public void reload(final long previousReload) {
107 final Optional<URL> modifiedClasspathLocations = Iterables.tryFind(getConfiguration().getClasspathLocations(), new Predicate<URL>() {
108 @Override
109 public boolean apply(URL url) {
110 URLConnection urlConnection = null;
111 try {
112 urlConnection = url.openConnection();
113 return urlConnection.getLastModified() > previousReload;
114 } catch (IOException e) {
115 return false;
116 } finally {
117 URLConnectionUtil.closeURLConnection(urlConnection);
118 }
119 }
120 });
121
122 if (!modifiedClasspathLocations.isPresent()) {
123 log.debug("No changes detected in any monitored classpath location, skipping classpath service reloading iteration");
124 return;
125 }
126
127 final Map<String, ClasspathEntry> currentEntries = getClasspathEntryMappings();
128 final Map<String, ClasspathEntry> newEntries = resolveResources();
129
130 Set<ClasspathResourceChange> resourceChanges = newHashSet();
131
132 final Set<String> deletedResources = newHashSet(difference(currentEntries.keySet(), newEntries.keySet()));
133 for (final String deletedResourcePath : deletedResources) {
134 log.info("Classpath resource deleted at [{}]", deletedResourcePath);
135 resourceChanges.add(new ClasspathResourceChange(deletedResourcePath, REMOVED));
136 }
137
138 final Set<String> addedResources = newHashSet(difference(newEntries.keySet(), currentEntries.keySet()));
139 for (final String addedResourcePath : addedResources) {
140 log.info("Classpath resource added at [{}]", addedResourcePath);
141 resourceChanges.add(new ClasspathResourceChange(addedResourcePath, ADDED));
142 }
143
144
145 final Pattern monitoredResourcePattern = getConfiguration().getMonitoredResourcePattern();
146 final Set<String> modifiedResources = Maps.filterEntries(newEntries, new Predicate<Map.Entry<String, ClasspathEntry>>() {
147 @Override
148 public boolean apply(Map.Entry<String, ClasspathEntry> entry) {
149 final String path = entry.getKey();
150 final ClasspathEntry cpEntry = entry.getValue();
151 return !addedResources.contains(path) && !deletedResources.contains(path) && monitoredResourcePattern.matcher(path).matches() && cpEntry.getLastModified() > previousReload;
152 }
153 }).keySet();
154
155 for (final String modifiedResourcePath : modifiedResources) {
156 log.info("Classpath resource modified at [{}]", modifiedResourcePath);
157 resourceChanges.add(new ClasspathResourceChange(modifiedResourcePath, MODIFIED));
158 }
159
160 clearAllMappings();
161 addMappings(newEntries);
162
163 for (final ClasspathResourceCallback handler : resourceModificationCallbacks) {
164 for (final ClasspathResourceChange resourceChange : resourceChanges) {
165 handler.onClasspathResourceChanged(resourceChange);
166 }
167 }
168 }
169
170 @Override
171 public void startMonitoring() {
172 if (monitor != null) {
173 throw new IllegalStateException("Classpath service monitoring has already been started");
174 }
175
176 monitor = Executors.newSingleThreadScheduledExecutor();
177 monitor.scheduleWithFixedDelay(new Runnable() {
178 private long lastScan = System.currentTimeMillis();
179
180 @Override
181 public void run() {
182
183 try {
184 reload(lastScan);
185 lastScan = System.currentTimeMillis();
186 } catch (Exception e) {
187 log.error("Classpath resource monitoring iteration failed due to: {}", e.getMessage(), e);
188 }
189 }
190 }, SCAN_INTERVAL, SCAN_INTERVAL, TimeUnit.SECONDS);
191 }
192
193 @Override
194 public void stopMonitoring() {
195 monitor.shutdownNow();
196 boolean terminated = false;
197 try {
198 terminated = monitor.awaitTermination(1, TimeUnit.SECONDS);
199 } catch (InterruptedException e) {
200 log.error("Exception during monitor termination", e);
201 }
202
203 if (terminated) {
204 log.info("{} terminated.", getClass().getSimpleName());
205 } else {
206 log.warn("{} not terminated, some tasks are still running.", getClass().getSimpleName());
207 }
208 }
209
210
211
212
213 public interface ClasspathResourceCallback {
214 void onClasspathResourceChanged(ClasspathResourceChange change);
215 }
216
217
218
219
220 @Value
221 public class ClasspathResourceChange {
222 String changedResourcePath;
223 ResourceOriginChange.Type type;
224 }
225 }