Skip to content

Commit 4fed884

Browse files
committed
jooby:run maven plugin shouldn't fork to a new JVM fix #405
1 parent 01e8c49 commit 4fed884

8 files changed

Lines changed: 666 additions & 251 deletions

File tree

jooby-hotreload/src/main/java/org/jooby/hotreload/AppModule.java

Lines changed: 179 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,38 +22,48 @@
2222
import java.io.FileInputStream;
2323
import java.io.IOException;
2424
import java.io.InputStream;
25+
import java.io.PrintWriter;
26+
import java.io.StringWriter;
2527
import java.lang.reflect.InvocationTargetException;
2628
import java.lang.reflect.Method;
2729
import java.nio.file.FileSystems;
2830
import java.nio.file.Path;
2931
import java.nio.file.PathMatcher;
3032
import java.nio.file.WatchEvent.Kind;
33+
import java.time.LocalTime;
34+
import java.time.format.DateTimeFormatter;
3135
import java.util.ArrayList;
32-
import java.util.Arrays;
3336
import java.util.List;
3437
import java.util.Map.Entry;
3538
import java.util.Properties;
3639
import java.util.concurrent.ExecutorService;
3740
import java.util.concurrent.Executors;
41+
import java.util.concurrent.atomic.AtomicReference;
3842

3943
import org.jboss.modules.Module;
4044
import org.jboss.modules.ModuleClassLoader;
4145
import org.jboss.modules.ModuleIdentifier;
42-
import org.jboss.modules.log.StreamModuleLogger;
46+
import org.jboss.modules.ModuleLoader;
47+
import org.jboss.modules.log.ModuleLogger;
4348

4449
public class AppModule {
4550

46-
public static boolean DEBUG;
51+
private static boolean DEBUG;
4752

48-
public static boolean TRACE;
53+
private static boolean TRACE;
54+
55+
static {
56+
logLevel();
57+
}
4958

5059
private AppModuleLoader loader;
51-
private Path[] paths = {new File(System.getProperty("user.dir")).toPath() };
60+
private File basedir = new File(System.getProperty("user.dir"));
5261
private ExecutorService executor;
5362
private Watcher scanner;
5463
private PathMatcher includes;
5564
private PathMatcher excludes;
5665
private volatile Object app;
66+
private AtomicReference<String> hash = new AtomicReference<String>("");
5767
private ModuleIdentifier mId;
5868
private String mainClass;
5969
private volatile Module module;
@@ -64,7 +74,9 @@ public AppModule(final String mId, final String mainClass, final File... cp)
6474
loader = AppModuleLoader.build(mId, mainClass, cp);
6575
this.mId = ModuleIdentifier.create(mId);
6676
this.executor = Executors.newSingleThreadExecutor(task -> new Thread(task, "HotSwap"));
67-
this.scanner = new Watcher(this::onChange, paths);
77+
this.scanner = new Watcher(this::onChange, new Path[]{basedir.toPath() });
78+
includes("**/*.class,**/*.conf,**/*.properties,*.js, src/*.js");
79+
excludes("");
6880
}
6981

7082
public static void main(final String[] args) throws Exception {
@@ -98,17 +110,12 @@ public static void main(final String[] args) throws Exception {
98110
}
99111
}
100112
// set log level, once we call setSystemProps
101-
DEBUG = Integer.getInteger("logLevel", 2) == 1;
102-
TRACE = Integer.getInteger("logLevel", 2) == 0;
113+
logLevel();
103114

104115
if (cp.isEmpty()) {
105116
cp.add(new File(System.getProperty("user.dir")));
106117
}
107118

108-
if (TRACE) {
109-
Module.setModuleLogger(new StreamModuleLogger(System.out));
110-
}
111-
112119
AppModule launcher = new AppModule(args[0], args[1], cp.toArray(new File[cp.size()]))
113120
.includes(includes)
114121
.excludes(excludes);
@@ -132,9 +139,9 @@ private static void setSystemProperties(final File sysprops) throws IOException
132139
}
133140

134141
public void run() {
135-
System.out.printf("Hotswap available on: %s%n", Arrays.toString(paths));
136-
System.out.printf(" includes: %s%n", includes);
137-
System.out.printf(" excludes: %s%n", excludes);
142+
info("Hotswap available on: %s", basedir.getPath());
143+
info(" includes: %s", includes);
144+
info(" excludes: %s", excludes);
138145

139146
this.scanner.start();
140147
this.startApp();
@@ -144,6 +151,7 @@ private void startApp() {
144151
if (app != null) {
145152
stopApp(app);
146153
}
154+
debug("scheduling: %s", mainClass);
147155
executor.execute(() -> {
148156
ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
149157
try {
@@ -162,14 +170,14 @@ private void startApp() {
162170
this.app = mcloader.loadClass(mainClass)
163171
.getDeclaredConstructors()[0].newInstance();
164172
}
173+
debug("starting: %s", mainClass);
165174
app.getClass().getMethod("start").invoke(app);
166175
} catch (Throwable ex) {
167-
System.err.println(mainClass + ".start() resulted in error");
168176
Throwable cause = ex;
169177
if (ex instanceof InvocationTargetException) {
170178
cause = ((InvocationTargetException) ex).getTargetException();
171179
}
172-
cause.printStackTrace();
180+
error("%s.start() resulted in error", mainClass, cause);
173181
} finally {
174182
Thread.currentThread().setContextClassLoader(ctxLoader);
175183
}
@@ -178,12 +186,13 @@ private void startApp() {
178186

179187
private void stopApp(final Object app) {
180188
try {
189+
debug("stopping: %s", mainClass);
181190
app.getClass().getMethod("stop").invoke(app);
182191
} catch (Throwable ex) {
183-
System.err.println(app.getClass().getName() + ".stop() resulted in error");
184-
ex.printStackTrace();
192+
error("%s.stop() resulted in error", mainClass, ex);
185193
} finally {
186194
try {
195+
debug("unloading: %s", mainClass);
187196
loader.unload(module);
188197
} catch (Throwable ex) {
189198
// sshhhh
@@ -192,34 +201,43 @@ private void stopApp(final Object app) {
192201

193202
}
194203

195-
private AppModule includes(final String includes) {
204+
public AppModule includes(final String includes) {
196205
this.includes = pathMatcher(includes);
197206
return this;
198207
}
199208

200-
private AppModule excludes(final String excludes) {
209+
public AppModule excludes(final String excludes) {
201210
this.excludes = pathMatcher(excludes);
202211
return this;
203212
}
204213

205214
private void onChange(final Kind<?> kind, final Path path) {
206215
try {
216+
debug("OnChange: %s(%s)", path, kind);
207217
Path candidate = relativePath(path);
208218
if (candidate == null || !includes.matches(candidate) || excludes.matches(candidate)) {
219+
debug("Ignoring change: %s", path);
209220
return;
210221
}
211-
// reload
212-
startApp();
222+
// weak hash check: avoid change on conf/* that are propagated to target/classs by maven.
223+
File f = candidate.toFile();
224+
String h = f.getName() + ":" + f.length();
225+
if (!hash.getAndSet(h).equals(h)) {
226+
debug("File change detected: %s", path);
227+
// reload
228+
startApp();
229+
} else {
230+
debug("Ignoring change: %s", path);
231+
}
213232
} catch (Exception ex) {
214233
ex.printStackTrace();
215234
}
216235
}
217236

218237
private Path relativePath(final Path path) {
219-
for (Path root : paths) {
220-
if (path.startsWith(root)) {
221-
return root.relativize(path);
222-
}
238+
Path root = basedir.toPath();
239+
if (path.startsWith(root)) {
240+
return root.relativize(path);
223241
}
224242
return null;
225243
}
@@ -248,4 +266,138 @@ public String toString() {
248266
};
249267
}
250268

269+
private static void logLevel() {
270+
DEBUG = "debug".equalsIgnoreCase(System.getProperty("logLevel", ""));
271+
272+
TRACE = "trace".equalsIgnoreCase(System.getProperty("logLevel", ""));
273+
274+
if (TRACE) {
275+
Module.setModuleLogger(new ModuleLogger() {
276+
277+
@Override
278+
public void trace(final Throwable t, final String format, final Object arg1,
279+
final Object arg2, final Object arg3) {
280+
AppModule.trace(format, arg1, arg2, arg3, t);
281+
}
282+
283+
@Override
284+
public void trace(final Throwable t, final String format, final Object arg1,
285+
final Object arg2) {
286+
AppModule.trace(format, arg1, arg2, t);
287+
}
288+
289+
@Override
290+
public void trace(final String format, final Object arg1, final Object arg2,
291+
final Object arg3) {
292+
AppModule.trace(format, arg1, arg2, arg3);
293+
}
294+
295+
@Override
296+
public void trace(final Throwable t, final String format, final Object... args) {
297+
Object[] values = new Object[args.length + 1];
298+
System.arraycopy(args, 0, values, 0, args.length);
299+
values[values.length - 1] = t;
300+
AppModule.trace(format, values);
301+
}
302+
303+
@Override
304+
public void trace(final Throwable t, final String format, final Object arg1) {
305+
AppModule.trace(format, arg1, t);
306+
}
307+
308+
@Override
309+
public void trace(final String format, final Object arg1, final Object arg2) {
310+
AppModule.trace(format, arg1, arg2);
311+
312+
}
313+
314+
@Override
315+
public void trace(final Throwable t, final String message) {
316+
AppModule.trace(message, t);
317+
}
318+
319+
@Override
320+
public void trace(final String format, final Object... args) {
321+
AppModule.trace(format, args);
322+
}
323+
324+
@Override
325+
public void trace(final String format, final Object arg1) {
326+
AppModule.trace(format, arg1);
327+
}
328+
329+
@Override
330+
public void trace(final String message) {
331+
AppModule.trace(message);
332+
}
333+
334+
@Override
335+
public void providerUnloadable(final String name, final ClassLoader loader) {
336+
}
337+
338+
@Override
339+
public void moduleDefined(final ModuleIdentifier identifier,
340+
final ModuleLoader moduleLoader) {
341+
}
342+
343+
@Override
344+
public void greeting() {
345+
}
346+
347+
@Override
348+
public void classDefined(final String name, final Module module) {
349+
}
350+
351+
@Override
352+
public void classDefineFailed(final Throwable throwable, final String className,
353+
final Module module) {
354+
}
355+
});
356+
}
357+
}
358+
359+
public static void info(final String message, final Object... args) {
360+
System.out.println(format(message, args));
361+
}
362+
363+
public static void error(final String message, final Object... args) {
364+
System.err.println(format(message, args));
365+
}
366+
367+
public static void debug(final String message, final Object... args) {
368+
if (DEBUG) {
369+
System.out.println(format(message, args));
370+
}
371+
}
372+
373+
public static void trace(final String message, final Object... args) {
374+
if (TRACE) {
375+
System.out.println(format(message, args));
376+
}
377+
}
378+
379+
private static String format(final String message, final Object... args) {
380+
Object[] values = args;
381+
Throwable x = null;
382+
if (args.length > 0) {
383+
if (args[args.length - 1] instanceof Throwable) {
384+
x = (Throwable) args[args.length - 1];
385+
values = new Object[args.length - 1];
386+
System.arraycopy(args, 0, values, 0, values.length);
387+
}
388+
}
389+
String msg = String.format(message, values);
390+
StringBuilder buff = new StringBuilder();
391+
buff.append("[HotSwap|")
392+
.append(Thread.currentThread().getName()).append("|")
393+
.append(LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss")))
394+
.append("]: ").append(msg);
395+
if (x != null) {
396+
buff.append("\n");
397+
StringWriter writer = new StringWriter();
398+
x.printStackTrace(new PrintWriter(writer));
399+
buff.append(writer);
400+
}
401+
return buff.toString();
402+
}
251403
}

jooby-hotreload/src/main/java/org/jooby/hotreload/AppModuleLoader.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,32 @@ public void unload(final Module module) {
7070
*/
7171
public static AppModuleLoader build(final String name, final String mainClass, final File... cp)
7272
throws Exception {
73-
Map<ModuleIdentifier, ModuleSpec> modules = newModule(name, mainClass, cp);
73+
Map<ModuleIdentifier, ModuleSpec> modules = newModule(name, mainClass, 0, "", cp);
7474
return new AppModuleLoader(modules);
7575
}
7676

7777
private static Map<ModuleIdentifier, ModuleSpec> newModule(final String name,
78-
final String mainClass, final File... cp)
78+
final String mainClass, final int level, final String prefix, final File... cp)
7979
throws Exception {
8080
Map<ModuleIdentifier, ModuleSpec> modules = new HashMap<>();
8181

82-
ModuleSpec.Builder builder = ModuleSpec.build(ModuleIdentifier.fromString(name));
82+
String mId = name.replace(".jar", "");
83+
ModuleSpec.Builder builder = ModuleSpec.build(ModuleIdentifier.fromString(mId));
8384

85+
int l = (prefix.length() + mId.length() + level);
86+
AppModule.debug("%1$" + l + "s", prefix + mId);
8487
for (File file : cp) {
85-
if (AppModule.DEBUG) {
86-
System.out.println("adding " + file);
87-
}
88+
String fname = "└── " + file.getAbsolutePath();
8889
if (file.getName().startsWith("j2v8") && !name.equals(file.getName())) {
89-
ModuleSpec dependency = newModule(file.getName(), null, file).values().iterator().next();
90+
ModuleSpec dependency = newModule(file.getName(), null, level + 2, "└── ", file)
91+
.values()
92+
.iterator()
93+
.next();
9094
builder.addDependency(
9195
DependencySpec.createModuleDependencySpec(dependency.getModuleIdentifier()));
9296
modules.put(dependency.getModuleIdentifier(), dependency);
9397
} else {
98+
AppModule.debug("%1$" + (fname.length() + level + 2) + "s", fname);
9499
if (file.getName().endsWith(".jar")) {
95100
builder.addResourceRoot(ResourceLoaderSpec
96101
.createResourceLoaderSpec(ResourceLoaders
@@ -157,4 +162,5 @@ private static Set<String> pkgs(final Reader reader) throws IOException {
157162
return pkgs;
158163
}
159164
}
165+
160166
}

0 commit comments

Comments
 (0)