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;
/** 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;
throw new IOException("unexpected class not found: " + e.getMessage());
}
- r.execute(root, out);
+ r.execute(p, root, out);
}
/** Sends a request to the server. */
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 {
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;
}
// 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 {
}).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));
// 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;
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 {
}
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) {
} 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); }