update to handle returning old version from put() function in JS and functional rm...
authorcrawshaw <crawshaw@ibex.org>
Sun, 5 Dec 2004 22:32:27 +0000 (22:32 +0000)
committercrawshaw <crawshaw@ibex.org>
Sun, 5 Dec 2004 22:32:27 +0000 (22:32 +0000)
darcs-hash:20041205223227-2eb37-0de3f6e68ae8768e94680c9346ed6f74fb4a30b2.gz

src/java/org/ibex/xt/Servlet.java
src/java/org/ibex/xt/shell/Command.java
src/java/org/ibex/xt/shell/JSRemote.java
src/java/org/ibex/xt/shell/Servlet.java
src/java/org/ibex/xt/shell/Shell.java

index 71eedbb..78d1d91 100644 (file)
@@ -96,13 +96,13 @@ public class Servlet extends HttpServlet {
                 //#end
                 return super.get(key);
             }
-            public void put(Object key, Object val) throws JSExn {
+            public Object put(Object key, Object val) throws JSExn {
                 //#switch(JS.toString(key))
                 case "created":       throw new JSExn("can not set session.created");
                 case "accessed":      throw new JSExn("can not set session.accessed");
                 case "invalidate":    throw new JSExn("can not set session.invalidate");
                 //#end
-                super.put(key, val);
+                return super.put(key, val);
             }
             public Object callMethod(Object method, final Object a, final Object b, Object c, Object[] rest, int nargs)
                 throws JSExn {
@@ -128,9 +128,10 @@ public class Servlet extends HttpServlet {
             private List keys = null;
             public Object get(Object key) {
                 return request.getSession(true).getAttribute(JS.toString(key)); }
-            public void put(Object key, Object val) {
+            public Object put(Object key, Object val) {
                 if (val == null) request.getSession(true).removeAttribute(JS.toString(key));
-                else request.setAttribute(JS.toString(key), val); }
+                else request.setAttribute(JS.toString(key), val);
+                return null; }
             public Collection keys() {
                 return keys == null ? keys = Collections.list(request.getSession(true).getAttributeNames()) : keys; }
         };
@@ -141,8 +142,8 @@ public class Servlet extends HttpServlet {
                 return keys == null ? keys = Collections.list(request.getHeaderNames()) : keys; }
         };
         private JS responseHeader = new JS() {
-            public void put(Object key, Object val) {
-                response.setHeader(JS.toString(key), JS.toString(val)); }
+            public Object put(Object key, Object val) {
+                response.setHeader(JS.toString(key), JS.toString(val)); return null; }
         };
 
 
@@ -150,8 +151,8 @@ public class Servlet extends HttpServlet {
         private class Sub extends JS {
             Object key;
             Sub(Object key) { this.key = key; }
-            public void put(Object key, Object val) throws JSExn {
-                Scope.this.put(JS.toString(this.key) + "." + JS.toString(key), val); }
+            public Object put(Object key, Object val) throws JSExn {
+                return Scope.this.put(JS.toString(this.key) + "." + JS.toString(key), val); }
             public Object get(Object key) throws JSExn {
                 return Scope.this.get(JS.toString(this.key) + "." + JS.toString(key)); }
             public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
@@ -206,13 +207,14 @@ public class Servlet extends HttpServlet {
             //#end
             return null;
         }
-        public void put(Object key, Object val) throws JSExn {
+        public Object put(Object key, Object val) throws JSExn {
             try {
             //#switch(JS.toString(key))
             case "response.code":         response.setStatus(JS.toInt(val));
             case "response.redirect":     response.sendRedirect(JS.toString(val));
             case "response.contentType":  response.setContentType(JS.toString(val));
             //#end
+            return null;
             } catch (IOException e) {
                 throw new JSExn(e);
             }
index 48fb329..04ca7b1 100644 (file)
@@ -1,5 +1,6 @@
 package org.ibex.xt.shell;
 
+import java.io.StringReader;
 import java.io.Writer;
 import java.io.IOException;
 
@@ -39,14 +40,14 @@ public abstract class Command {
         w.write("\n");
     }
 
-    public List fromShellPath(String s) throws Shell.NoSuchPathException {
+    public List fromShellPath(String s) throws Shell.BadPathException {
         return fromShellPath(s, null);
     }
 
     /** Converts a shell path "/foo/etc/../bar" to its component form,
      *  { "foo", "bar" } and loads it into the returned list.
      *  Handles relative positioning to shell.getPath(). */
-    public List fromShellPath(String s, List l) throws Shell.NoSuchPathException {
+    public List fromShellPath(String s, List l) throws Shell.BadPathException {
         if (s == null) return null;
         if (l == null) l = new ArrayList();
 
@@ -124,7 +125,7 @@ public abstract class Command {
                 Object key = path.remove(path.size() - 1);
                 Object po = shell.getFromPath(path.toArray());
                 if (po == null || !(po instanceof JS))
-                    throw new Shell.NoSuchPathException();
+                    throw new Shell.BadPathException();
                 JS cur = (JS)po;
 
                 if (key instanceof String &&
@@ -145,7 +146,7 @@ public abstract class Command {
                     w.write(key.toString());
                     w.write("\n");
                 }
-            } catch (Shell.NoSuchPathException e) {
+            } catch (Shell.BadPathException e) {
                 w.write("error: no such path: ");
                 w.write(p);
                 w.write("\n");
@@ -159,6 +160,86 @@ public abstract class Command {
         }
     }
 
+    public static class Rm extends Command {
+        public Rm(Shell s) { super(s); }
+        public String name() { return "rm"; }
+        public String params() { return "[options] [path]"; }
+        public String summary() { return "Removes objects."; }
+        public String help() { return "Removes objects."; } // FIXME info
+        public void execute(Writer w, String[] c) throws IOException {
+            if (c.length == 1) { usage(w); return; }
+
+            boolean force = false; // FIXME provide ability to set
+
+            StringBuffer func = new StringBuffer();
+
+            for (int ic=1; ic < c.length; ic++) {
+                String p = c.length == 1 ? "*" : c[ic];
+                if (p.endsWith("/")) p += "*";
+
+                try {
+                    // get the base of the path
+                    List path = fromShellPath(p);
+                    Object key = path.remove(path.size() - 1);
+                    Object po = shell.getFromPath(path.toArray());
+                    if (po == null || !(po instanceof JS))
+                        throw new Shell.BadPathException();
+                    JS cur = (JS)po;
+
+                    if (cur.containsKey(key)) {
+                        Object o = cur.get(key);
+                        if (!force && o != null && o instanceof JS && ((JS)o).keys().size() > 0)
+                            throw new Shell.BadPathException("key is not empty");
+                        func.append("prevalent.");
+                        for(int i=0; i < path.size(); i++) {
+                            func.append(path.get(i)); func.append('.'); }
+                        func.append("Delete(\"");
+                        func.append(key.toString());
+                        func.append("\");\n");
+                    } else if (key instanceof String && ((String)key).indexOf('*') >= 0) {
+                        String last = (String)key;
+                        last = last.replaceAll("\\.", "\\.");
+                        last = last.replaceAll("\\*", ".*");
+                        Pattern pat = Pattern.compile(last);
+                        Collection curkeys = cur.keys();
+                        if (curkeys.size() == 0) throw new Shell.BadPathException();
+                        Iterator it = curkeys.iterator(); while (it.hasNext()) {
+                            Object o = it.next();
+                            if (o == null || !(o instanceof String)) continue;
+                            String s = (String)o;
+                            if (!pat.matcher(s).matches()) continue;
+
+                            func.append("prevalent.");
+                            for(int i=0; i < path.size(); i++) {
+                                func.append(path.get(i)); func.append('.'); }
+                            func.append("Delete(\"");
+                            func.append(s);
+                            func.append("\");\n");
+                        }
+                    } else throw new Shell.BadPathException();
+
+                } catch (JSExn e) {
+                    w.write("error: cannot remove '");
+                    w.write(p);
+                    w.write("': ");
+                    w.write(e.getMessage());
+                    w.write("\n");
+                    return;
+                } catch (Shell.BadPathException e) {
+                    w.write("error: cannot remove '");
+                    w.write(p);
+                    w.write("': ");
+                    w.write(e.getMessage() != null ? e.getMessage() : "no such path");
+                    w.write("\n");
+                    return;
+                }
+            }
+
+            shell.transaction(JS.fromReader(
+                "rm-transaction", 0, new StringReader(func.toString())));
+        }
+    }
+
     public static class Pwd extends Command {
         public Pwd(Shell s) { super(s); }
         public String name() { return "pwd"; }
@@ -194,7 +275,7 @@ public abstract class Command {
             String path = c.length == 1 ? "/" : c[1];
 
             try { shell.setPath(fromShellPath(path).toArray()); }
-            catch (Shell.NoSuchPathException e) {
+            catch (Shell.BadPathException e) {
                 w.write("error: no such path: ");
                 w.write(path);
                 w.write("\n");
@@ -202,15 +283,4 @@ public abstract class Command {
         }
     }
 
-    public static class Rm extends Command {
-        public Rm(Shell s) { super(s); }
-        public String name() { return "rm"; }
-        public String params() { return "[options] [path]"; }
-        public String summary() { return "Removes objects."; }
-        public String help() { return "Removes objects."; } // FIXME
-        public void execute(Writer w, String[] c) throws IOException {
-            if (c.length == 1) { usage(w); return; }
-
-        }
-    }
 }
index f7886d8..4b3e475 100644 (file)
@@ -8,6 +8,9 @@ import org.ibex.js.*;
 import org.ibex.util.*;
 import org.ibex.util.Collections;
 
+import org.ibex.xt.Prevalence;
+import org.prevayler.*;
+
 public class JSRemote extends JS {
     public static final int VERSION = 1;
 
@@ -22,7 +25,7 @@ public class JSRemote extends JS {
 
     /** Receives a Request object from a client on <tt>in</tt>
      *  and writes a response on <tt>out</tt> */
-    public static void receive(JS root, ObjectInputStream in, ObjectOutputStream out)
+    public static void receive(Prevayler p, JS root, ObjectInputStream in, ObjectOutputStream out)
                                throws IOException {
         out.writeInt(VERSION); if (in.readInt() != VERSION) return;
 
@@ -37,7 +40,7 @@ public class JSRemote extends JS {
             throw new IOException("unexpected class not found: " + e.getMessage());
         }
 
-        r.execute(root, out);
+        r.execute(p, root, out);
     }
 
     /** Sends a request to the server. */
@@ -63,6 +66,28 @@ public class JSRemote extends JS {
         return in;
     }
 
+    /** FEATURE: It's questionable as to whether this belongs here. JSRemote shouldn't
+     *  really know anything about prevayler, but that rquires another layer between
+     *  this class and the http layer. Maybe not a bad idea. */
+    public void transaction(final JS t) {
+        Object resp;
+        try {
+            resp = send(new Request(null) {
+                protected void execute() throws JSExn, IOException {
+                    try {
+                        prevayler.execute(new Prevalence.JSTransaction(t));
+                        out.writeObject(null);
+                    } catch (Exception e) { e.printStackTrace(); out.writeObject(e); }
+                }
+            }).readObject();
+        } catch (Exception e) {
+            throw new RuntimeException("transaction failed", e);
+        }
+
+        if (resp != null && resp instanceof Exception)
+            throw new RuntimeException("transaction failed", (Exception)resp);
+    }
+
     public Collection keys() { return keys == null ? keys = new Keys() : keys.update(); }
 
     public Object get(Object k) throws JSExn {
@@ -82,6 +107,7 @@ public class JSRemote extends JS {
             Object o = in.readObject(); in.close();
 
             if (o == null) return null;
+            else if (o instanceof JS) return new JSImmutable((JS)o);
             else if (o instanceof Exception) throw (Exception)o;
             else return o;
         } catch (JSExn e) { throw e;
@@ -89,12 +115,12 @@ public class JSRemote extends JS {
     }
 
     // FIXME: unroll JSRemote if it is a value
-    public void put(Object k, final Object value) throws JSExn {
-        try { send(new Request(path, k) {
+    public Object put(Object k, final Object value) throws JSExn {
+        try { return send(new Request(path, k) {
             protected void execute() throws JSExn, IOException {
-                scope.put(key, value);
+                out.writeObject(scope.put(key, value));
             }
-        }); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
+        }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
     }
 
     public Object remove(Object k) throws JSExn {
@@ -105,7 +131,7 @@ public class JSRemote extends JS {
         }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
     }
 
-    public boolean containsKey(Object k) throws JSExn {
+    public boolean containsKey(Object k) {
         try { return send(new Request(path, k) {
             protected void execute() throws JSExn, IOException {
                 out.writeBoolean(scope.containsKey(key));
@@ -134,6 +160,7 @@ public class JSRemote extends JS {
     // FIXME: map trap functions to server
 
     public static abstract class Request implements Serializable {
+        protected Prevayler prevayler;
         protected ObjectOutputStream out;
         protected JS scope;
         protected String path;
@@ -157,7 +184,8 @@ public class JSRemote extends JS {
 
             this.path = path;
         }
-        public void execute(JS r, ObjectOutputStream o) throws IOException {
+        public void execute(Prevayler p, JS r, ObjectOutputStream o) throws IOException {
+            prevayler = p;
             out = o;
             scope = r;
             try { 
@@ -175,21 +203,66 @@ public class JSRemote extends JS {
                 }
                 execute();
             } catch(JSExn e) { out.writeObject(e); }
+            prevayler = null;
             out = null;
             scope = null;
         }
         protected abstract void execute() throws JSExn, IOException;
     }
 
+    public static class JSImmutable extends JS {
+        private final JS wrapped;
+        public JSImmutable(JS toWrap) { wrapped = toWrap; }
+
+        public Collection keys() throws JSExn {
+            return Collections.unmodifiableCollection(wrapped.keys()); }
+        public Object get(Object key) throws JSExn { return wrapped.get(key); }
+        public boolean containsKey(Object key) { return wrapped.containsKey(key); }
+        public Object call(Object a0, Object a1, Object a2, Object[] r, int n)
+            throws JSExn { return wrapped.call(a0, a1, a2, r, n); }
+
+        public Object callMethod(Object m, Object a0, Object a1, Object a2, Object[] r, int n)
+            throws JSExn { throw new JSExn("immutable JS"); }
+        public Object put(Object k, Object v) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public Object remove(Object k) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public void putAndTriggerTraps(Object k, Object v) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public Object getAndTriggerTraps(Object k) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        protected boolean isTrappable(Object n, boolean i) { return false; }
+    }
+
     private class Keys extends AbstractCollection implements Serializable {
+        private transient boolean updating = false;
         private transient final List items =
             Collections.synchronizedList(new ArrayList());
-        private int modCount = 0, size = -1;
+        private transient int modCount = 0;
+
+        private int size = -1;
 
-        public Iterator iterator() { update(); return new KeyIt(); }
         public int size() { if (size == -1) update(); return size; }
+        public Iterator iterator() { updateList(); return new KeyIt(); }
 
         private synchronized Keys update() {
+            if (updating) return this;
+            modCount++; items.clear();
+            try {
+                final ObjectInputStream in = send(new Request(path) {
+                    protected void execute() throws JSExn, IOException {
+                        Collection c = scope.keys(); out.writeInt(c.size());
+                    }
+                });
+                size = in.readInt();
+            } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
+            return this;
+        }
+
+        private synchronized Keys updateList() {
+            if (updating) return this;
+            updating = true;
             modCount++; items.clear();
             try {
                 final ObjectInputStream in = send(new Request(path) {
@@ -214,6 +287,8 @@ public class JSRemote extends JS {
                     } catch (Exception e) {
                         size = -1; items.clear();
                         throw new RuntimeException("JSRemote", e);
+                    } finally {
+                        synchronized (Keys.this) { Keys.this.updating = false; }
                     }
                 }}.start();
             } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
index 70b89d9..fc4cbb4 100644 (file)
@@ -24,7 +24,7 @@ public class Servlet extends HttpServlet {
     public void doPost(HttpServletRequest rq, HttpServletResponse rs) throws IOException {
         ObjectInputStream in = new ObjectInputStream(rq.getInputStream());
         ObjectOutputStream out = new ObjectOutputStream(rs.getOutputStream());
-        JSRemote.receive(prevalent, in, out);
+        JSRemote.receive(prevayler, prevalent, in, out);
         out.flush();
     }
 }
index f677f28..da77e94 100644 (file)
@@ -40,7 +40,7 @@ public class Shell {
 
     /** Returns the object represented by the given path,
      *  ignoring the current shell path context.*/
-    public Object getFromPath(Object[] path) throws NoSuchPathException, JSExn {
+    public Object getFromPath(Object[] path) throws BadPathException, JSExn {
         if (path.length == 0) return root;
 
         if (root instanceof JSRemote) {
@@ -54,21 +54,30 @@ public class Shell {
             JS cur = root;
             for (int i=0; i < path.length - 1; i++) {
                 Object o =  cur.get(path[i]);
-                if (o == null || !(o instanceof JS)) throw new Shell.NoSuchPathException();
+                if (o == null || !(o instanceof JS)) throw new Shell.BadPathException();
                 cur = (JS)o;
             }
             return cur.get(path[path.length - 1]);
         }
     }
 
+    public void transaction(JS t) {
+        if (root instanceof JSRemote) {
+            ((JSRemote)root).transaction(
+                JS.cloneWithNewParentScope(t, new JSScope(null)));
+        } else {
+            // FIXME JS.eval(JS.cloneWithNewParentScope(t, root));
+        }
+    }
+
     /** Set the current path of the shell, modifiying the result of getScope(). */
-    public void setPath(Object[] s) throws NoSuchPathException {
+    public void setPath(Object[] s) throws BadPathException {
         JS cur = root;
         if (s == null) s = new Object[0];
         for (int i=0; i < s.length; i++) {
             Object o;
-            try { o = cur.get(s[i]); } catch (JSExn e) { throw new NoSuchPathException(); }
-            if (o == null || !(o instanceof JS)) throw new NoSuchPathException();
+            try { o = cur.get(s[i]); } catch (JSExn e) { throw new BadPathException(); }
+            if (o == null || !(o instanceof JS)) throw new BadPathException();
             cur = (JS)o;
         }
         scope = cur;
@@ -174,5 +183,8 @@ public class Shell {
         }
     }
 
-    public static class NoSuchPathException extends Exception {}
+    public static class BadPathException extends Exception {
+        public BadPathException() {}
+        public BadPathException(String msg) { super(msg); }
+    }
 }