Add a VM shutdown hook in DirServer to shut down contexts properly.
[jsvc.git] / src / dolda / jsvc / scgi / DirServer.java
1 package dolda.jsvc.scgi;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.*;
6 import java.util.logging.*;
7 import dolda.jsvc.*;
8 import dolda.jsvc.util.*;
9 import dolda.jsvc.j2ee.PosixArgs;
10
11 public class DirServer extends Server {
12     private final Map<File, DSContext> contexts = new HashMap<File, DSContext>();
13     private final Environment env;
14     private final Logger logger = Logger.getLogger("dolda.jsvc.scgi.dirserver");
15     private Thread sdhook = null, main = null;
16     
17     public DirServer(ServerSocket sk, Environment env) {
18         super(sk);
19         this.env = env;
20     }
21
22     private DSContext context(File file) throws ThreadContext.CreateException {
23         synchronized(contexts) {
24             DSContext ctx = contexts.get(file);
25             String act = "loaded %s as %s";
26             if(ctx != null) {
27                 if(ctx.mtime < file.lastModified()) {
28                     ctx.tg.shutdown();
29                     contexts.remove(file);
30                     ctx = null;
31                     act = "reloaded %s as %s";
32                 }
33             }
34             if(ctx == null) {
35                 ctx = new DSContext(file, env);
36                 contexts.put(file, ctx);
37                 logger.config(String.format(act, file, ctx.name()));
38             }
39             return(ctx);
40         }
41     }
42
43     public void handle(Map<String, String> head, Socket sk) throws Exception {
44         String filename = head.get("SCRIPT_FILENAME");
45         if(filename == null)
46             throw(new Exception("Request for DirServer must contain SCRIPT_FILENAME"));
47         File file = new File(filename);
48         if(!file.exists() || !file.canRead())
49             throw(new Exception("Cannot access the requested JSvc file " + file.toString()));
50         DSContext ctx = context(file);
51         Request req = new ScgiRequest(sk, head);
52         RequestThread w = ctx.tg.respond(req);
53         w.start();
54     }
55     
56     private class ShutdownHandler extends Thread {
57         public void run() {
58             sdhook = null;
59             DirServer.this.stop();
60             try {
61                 main.join();
62             } catch(InterruptedException e) {}
63         }
64     }
65
66     protected void shutdown() {
67         try {
68             if(sdhook != null)
69                 Runtime.getRuntime().removeShutdownHook(sdhook);
70         } catch(Exception e) {}
71         synchronized(contexts) {
72             for(Iterator<Map.Entry<File, DSContext>> i = contexts.entrySet().iterator(); i.hasNext();) {
73                 Map.Entry<File, DSContext> e = i.next();
74                 DSContext ctx = e.getValue();
75                 i.remove();
76                 ctx.tg.shutdown();
77             }
78         }
79         super.shutdown();
80     }
81
82     private static void usage(PrintStream out) {
83         out.println("usage: dolda.jsvc.scgi.DirServer [-h] [-e CHARSET] [-d DATADIR] PORT");
84     }
85     
86     public static void main(String[] args) {
87         PosixArgs opt = PosixArgs.getopt(args, "h");
88         if(opt == null) {
89             usage(System.err);
90             System.exit(1);
91         }
92         String charset = null;
93         File datroot = null;
94         for(char c : opt.parsed()) {
95             switch(c) {
96             case 'e':
97                 charset = opt.arg;
98                 break;
99             case 'd':
100                 datroot = new File(opt.arg);
101                 if(!datroot.exists() || !datroot.isDirectory()) {
102                     System.err.println(opt.arg + ": no such directory");
103                     System.exit(1);
104                 }
105                 break;
106             case 'h':
107                 usage(System.out);
108                 return;
109             }
110         }
111         if(opt.rest.length < 1) {
112             usage(System.err);
113             System.exit(1);
114         }
115         Environment env = (datroot == null)?new Environment():new Environment(datroot);
116         env.initvm();
117         int port = Integer.parseInt(opt.rest[0]);
118         ServerSocket sk;
119         try {
120             sk = new ServerSocket(port);
121         } catch(IOException e) {
122             System.err.println("could not bind to port " + port + ": " + e.getMessage());
123             System.exit(1);
124             return; /* Because javac is stupid. :-/ */
125         }
126         DirServer s = new DirServer(sk, env);
127         if(charset != null)
128             s.headcs = charset;
129         
130         Runtime.getRuntime().addShutdownHook(s.sdhook = s.new ShutdownHandler());
131         s.main = new Thread(s, "SCGI server thread");
132         s.main.start();
133     }
134 }