833f8fc7f846c88901505fa3156da3c62cff715f
[org.ibex.xt.git] / src / org / ibex / xml / Servlet.java
1 package org.ibex.xml;
2 import org.ibex.js.*;
3 import org.ibex.util.*;
4 import org.ibex.io.*;
5 import java.io.*;
6 import java.net.*;
7 import java.util.*;
8 import javax.servlet.*;
9 import javax.servlet.http.*;
10 import com.thoughtworks.xstream.*;
11 import org.prevayler.*;
12 import org.prevayler.implementation.snapshot.*;
13
14
15 public class Servlet extends HttpServlet {
16
17     private ServletResolver resolver = new ServletResolver();
18     private class ServletResolver implements XML.Node.Stream.Resolver {
19         public XML.Node.Stream.Functor resolve(String uri) {
20             if (uri.indexOf(':') == -1) throw new RuntimeException("uri does not contain an method: " + uri);
21             String method = uri.substring(0, uri.indexOf(':'));
22             String rest = uri.substring(uri.indexOf(':'))+1;
23             //case "xtree":  return XTree.tag(rest);
24             //#switch(method)
25             case "webinf": return new Template(cx.getRealPath(rest));
26             case "java":   try { return (XML.Node.Stream.Functor)Class.forName(rest).newInstance(); }
27                            catch (Exception e) { throw new RuntimeException(e); }
28             //#end
29             throw new RuntimeException("unknown method " + method);    
30         }
31     }
32
33     public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
34         JSScope scope = new ServletScope(request, response);
35         String path = cx.getRealPath(((HttpServletRequest)request).getServletPath());
36         Reader xmlreader = new InputStreamReader(new FileInputStream(path));
37         XML.Node.Stream s = new JSRewriter(XML.Node.Stream.in(xmlreader), scope);
38         s.out(response.getWriter());
39     }
40
41     public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { doGet(request, response); }
42
43     public class ServletScope extends JSScope {
44         HttpServletRequest request;
45         HttpServletResponse response;
46         public ServletScope(ServletRequest request, ServletResponse response) {
47             super(null);
48             this.request = (HttpServletRequest)request;
49             this.response = (HttpServletResponse)response;
50         }
51         private JS params = new JS() {
52                 public Object get(Object key) { return request.getParameter(JS.toString(key)); }
53                 public Enumeration keys() { return request.getParameterNames(); }
54             };
55         private JS cookies = new JS() {
56                 /*
57                 public Object get(Object key) { return request.getCookie(JS.toString(key)); }
58                 public Enumeration keys() { return request.getCookieNames(); }
59                 */
60             };
61         private JS sessionAttributes = new JS() {
62                 public Object get(Object key) { return request.getSession(true).getAttribute(JS.toString(key)); }
63                 public void put(Object key, Object val) {
64                     if (val == null) request.getSession(true).removeAttribute(JS.toString(key));
65                     else request.setAttribute(JS.toString(key), val); }
66                 public Enumeration keys() { return request.getSession(true).getAttributeNames(); }
67             };
68         private JS requestHeader = new JS() {
69                 public Object get(Object key) { return request.getHeader(JS.toString(key)); }
70                 public Enumeration keys() { return request.getHeaderNames(); }
71             };
72         private JS responseHeader = new JS() {
73                 public void put(Object key, Object val) { response.setHeader(JS.toString(key), JS.toString(val)); }
74             };
75
76
77         /** lets us put multi-level get/put/call keys all in the same method */
78         private class Sub extends JS {
79             Object key;
80             Sub(Object key) { this.key = key; }
81             public void put(Object key, Object val) throws JSExn {
82                 ServletScope.this.put(JS.toString(this.key) + "." + JS.toString(key), val); }
83             public Object get(Object key) throws JSExn {
84                 return ServletScope.this.get(JS.toString(this.key) + "." + JS.toString(key)); }
85             public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
86                 return ServletScope.this.callMethod(this.key, a0, a1, a2, rest, nargs);
87             }
88             public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
89                 return ServletScope.this.callMethod(JS.toString(this.key) + "."
90                                                          + JS.toString(method), a0, a1, a2, rest, nargs);
91             }
92         }
93         private Sub getSub(String key) { return new Sub(key); }
94
95         public Object callMethod(Object method, final Object a, final Object b, Object c, Object[] rest, int nargs) throws JSExn {
96             //#switch(method)
97             case "prevalent.query":
98                 try {
99                     return prevayler.execute(new JSQuery(JS.cloneWithNewParentScope((JS)a, null)));
100                 } catch (Exception e) {
101                     e.printStackTrace();
102                     throw new RuntimeException(e); }
103
104             case "prevalent.execute":
105                 try {
106                     prevayler.execute(new JSTransaction(JS.cloneWithNewParentScope((JS)a, null)));
107                 } catch (Exception e) { 
108                     e.printStackTrace();
109                     throw new RuntimeException(e); }
110
111             case "session.invalidate":    request.getSession(true).invalidate(); return null;
112             //#end
113             return null;
114         }
115         public Object get(Object key) throws JSExn {
116             //#switch(key)
117             case "body":
118             case "arg":                   return null;
119             case "prevalent":             return getSub("prevalent");
120             case "prevalent.query":       return METHOD;
121             case "prevalent.execute":     return METHOD;
122             case "request":               return getSub("request");
123             case "request.user":          return request.getRemoteUser();
124             case "request.header":        return requestHeader;
125             case "request.method":        return request.getMethod();
126             case "request.remote":        return getSub("request.remote");
127             case "request.remote.ip":     return request.getRemoteAddr();
128             case "request.remote.host":   return request.getRemoteHost();
129             case "request.ssl":           return request.isSecure();
130             case "request.path":          return request.getPathInfo();
131             case "response":              return getSub("response");
132             case "response.header":       return responseHeader;
133             case "session":               return getSub("session");
134             case "session.attr":          return sessionAttributes;
135             case "session.created":       return new JSDate(request.getSession(true).getCreationTime());
136             case "session.accessed":      return new JSDate(request.getSession(true).getLastAccessedTime());
137             case "session.invalidate":    return METHOD;
138             case "params":                return params;
139             case "cookie":                return cookies;
140             //#end
141             return null;
142         }
143         public void put(Object key, Object val) throws JSExn {
144             try {
145             //#switch(JS.toString(key))
146             case "response.code":         response.setStatus(JS.toInt(val));
147             case "response.redirect":     response.sendRedirect(JS.toString(val));
148             case "response.contentType":  response.setContentType(JS.toString(val));
149             //#end
150             } catch (IOException e) {
151                 throw new JSExn(e);
152             }
153         }
154     }
155
156     // Prevalence //////////////////////////////////////////////////////////////////////////////
157     
158     static final Hashtable prevaylers = new Hashtable();
159     private Prevayler prevayler;
160     private JS prevalent;
161     private ServletContext cx = null;
162     public void destroy() {
163         try {
164             synchronized(this.getClass()) {
165                 Prevayler privatePrevayler = prevayler;
166                 if (prevayler == null) return;
167                 prevayler = null;
168                 prevaylers.remove(cx);
169                 prevayler.takeSnapshot();
170                 prevayler.close();
171             }
172         } catch (Exception e) {
173             e.printStackTrace();
174         }
175     }
176
177     private static class SnapshotThread extends Thread {
178         ServletContext cx;
179         public SnapshotThread(ServletContext cx) { this.cx = cx; }
180         public void run() {
181             try {
182                 Thread.sleep(10000);
183                 Prevayler privatePrevayler = (Prevayler)prevaylers.get(cx);
184                 if (privatePrevayler == null) return;
185                 privatePrevayler.takeSnapshot();
186             } catch (Exception e) {
187                 e.printStackTrace();
188             }
189         }
190     }
191
192     public void init(ServletConfig sc) throws ServletException {
193         try {
194             cx = sc.getServletContext();
195             synchronized(cx) {
196                 prevayler = (Prevayler)prevaylers.get(cx);
197                 if (prevalent == null) {
198                     PrevaylerFactory pf = new PrevaylerFactory();
199                     String base = cx.getRealPath("/") + "WEB-INF" + File.separatorChar + "prevalent";
200                     System.err.println("prevayling to " + base);
201                     pf.configurePrevalenceBase(base);
202                     XStreamSnapshotManager manager = new XStreamSnapshotManager(new JS(), base, null) {
203                             protected XStream createXStream() {
204                                 XStream xstream = new XStream();
205                                 xstream.alias("js", JS.class);
206                                 xstream.alias("jsdate", JSDate.class);
207                                 return xstream;
208                             }
209                         };
210                     System.err.println("configuring with " + manager);
211                     pf.configureSnapshotManager(manager);
212                     //pf.configureClassLoader(JSTransaction.class.getClassLoader());
213                     prevayler = pf.create();
214                     prevaylers.put(cx, prevayler);
215                     new SnapshotThread(cx).start();
216                 }
217             }
218             prevalent = (JS)prevayler.prevalentSystem();
219         } catch (Exception e) {
220             throw new ServletException(e);
221         }
222     }
223
224     public static class JSTransaction implements Transaction {
225         private JS js;
226         public JSTransaction(JS js) { this.js = js; }
227         public void executeOn(Object o, Date now) {
228             try {
229                 js.call(o, new JSDate(now.getTime()), null, null, 2);
230             } catch (Exception e) { throw new RuntimeException(e); }
231         }
232     }
233
234     public static class JSQuery implements Query {
235         private JS js;
236         public JSQuery(JS js) { this.js = js; }
237         public Object query(Object o, Date now) {
238             try {
239                 return js.call(o, new JSDate(now.getTime()), null, null, 2);
240             } catch (Exception e) { throw new RuntimeException(e); }
241         }
242     }
243
244 }