Catch and report NoClassDefFoundError in makehandler.
[jagi.git] / src / jagi / fs / JavaHandler.java
1 package jagi.fs;
2
3 import jagi.*;
4 import java.util.*;
5 import java.util.function.*;
6 import java.util.logging.*;
7 import java.lang.reflect.*;
8 import java.nio.file.*;
9
10 public class JavaHandler implements Function<Map<Object, Object>, Map<Object, Object>> {
11     private static final Logger log = Logger.getLogger("jagi-fs");
12     private final Map<Compiler.Module, Function<Map<Object, Object>, Map<Object, Object>>> handlers = new WeakHashMap<>();
13
14     public static class HandlerException extends RuntimeException {
15         public final Path file;
16
17         public HandlerException(Path file, String msg, Throwable cause) {
18             super(msg, cause);
19             this.file = file;
20         }
21         public HandlerException(Path file, String msg) {
22             this(file, msg, null);
23         }
24
25         public String getMessage() {
26             return(file + ": " + super.getMessage());
27         }
28     }
29
30     @SuppressWarnings("unchecked")
31     private static Function<Map<Object, Object>, Map<Object, Object>> makehandler(Compiler.Module mod) {
32         Class<?> main;
33         try {
34             main = mod.code.loadClass("Main");
35         } catch(ClassNotFoundException e) {
36             throw(new HandlerException(mod.file, "no Main class"));
37         } catch(NoClassDefFoundError e) {
38             throw(new HandlerException(mod.file, "class not found: " + e.getMessage()));
39         }
40         try {
41             Method wmain = main.getDeclaredMethod("wmain", String[].class);
42             int attr = wmain.getModifiers();
43             if(((attr & Modifier.STATIC) == 0) || ((attr & Modifier.PUBLIC) == 0))
44                 throw(new NoSuchMethodException());
45             Object handler = wmain.invoke(null, new Object[] {new String[] {}});
46             if(!(handler instanceof Function))
47                 throw(new HandlerException(mod.file, "wmain in " + main.getName() + " returned " + ((handler == null) ? "null" : ("a " + handler.getClass()))));
48             return((Function<Map<Object, Object>, Map<Object, Object>>)handler);
49         } catch(IllegalAccessException e) {
50             throw(new HandlerException(mod.file, "could not call wmain", e));
51         } catch(InvocationTargetException e) {
52             throw(new HandlerException(mod.file, "wmain failed", e.getCause()));
53         } catch(NoSuchMethodException e) {
54         }
55         if(Function.class.isAssignableFrom(main)) {
56             try {
57                 Constructor<? extends Function> cons = main.asSubclass(Function.class).getConstructor();
58                 Function handler = cons.newInstance();
59                 return((Function<Map<Object, Object>, Map<Object, Object>>)handler);
60             } catch(NoSuchMethodException e) {
61             } catch(InvocationTargetException e) {
62                 throw(new HandlerException(mod.file, "constructor failed", e.getCause()));
63             } catch(ReflectiveOperationException e) {
64                 throw(new HandlerException(mod.file, "could not construct Main", e));
65             }
66         }
67         throw(new HandlerException(mod.file, "no wmain and not directly applicable"));
68     }
69
70     private Function<Map<Object, Object>, Map<Object, Object>> gethandler(Compiler.File file) {
71         Compiler.Module mod = file.mod();
72         synchronized(handlers) {
73             Function<Map<Object, Object>, Map<Object, Object>> ret = handlers.get(mod);
74             if(ret == null)
75                 handlers.put(mod, ret = makehandler(mod));
76             return(ret);
77         }
78     }
79
80     public Map<Object, Object> apply(Map<Object, Object> req) {
81         Compiler.File file = Compiler.get().file(Paths.get((String)req.get("SCRIPT_FILENAME")));
82         try {
83             file.update();
84         } catch(Compiler.CompilationException e) {
85             log.warning(String.format("Could not compile %s:\n%s", file.name, e.messages()));
86             return(Utils.simpleerror(500, "Internal Error", "Could not load JAGI handler"));
87         } catch(Exception e) {
88             log.log(Level.WARNING, String.format("Error occurred when loading %s", file.name), e);
89             return(Utils.simpleerror(500, "Internal Error", "Could not load JAGI handler"));
90         }
91         Function<Map<Object, Object>, Map<Object, Object>> handler;
92         try {
93             handler = gethandler(file);
94         } catch(HandlerException e) {
95             Throwable cause = e.getCause();
96             if(cause != null)
97                 log.log(Level.WARNING, cause, e::getMessage);
98             else
99                 log.log(Level.WARNING, e::getMessage);
100             return(Utils.simpleerror(500, "Internal Error", "Invalid JAGI handler"));
101         }
102         return(handler.apply(req));
103     }
104 }