r9752 - in helma-ng/trunk: apps/demo apps/demo/skins modules/helma/webapp

[email protected] Thu, 14 May 2009 15:18:33 +0200 (CEST)
Newsgroups gmane.comp.java.helma.cvs
Message-ID <20090514131833.359493D0D6@mia>
Author: hannes
Date: 2009-05-14 15:18:33 +0200 (Thu, 14 May 2009)
New Revision: 9752

Modified:
   helma-ng/trunk/apps/demo/actions.js
   helma-ng/trunk/apps/demo/config.js
   helma-ng/trunk/apps/demo/skins/continuation.txt
   helma-ng/trunk/modules/helma/webapp/continuation.js
Log:
Rewrite helma/webapp/continuation not to use JS continuations at all, storing just the data in the http session instead, making the code much easier to read and run.

Details at http://dev.helma.org/trac/helma/changeset/9752

Modified: helma-ng/trunk/apps/demo/actions.js
===================================================================
--- helma-ng/trunk/apps/demo/actions.js	2009-05-14 13:18:26 UTC (rev 9751)
+++ helma-ng/trunk/apps/demo/actions.js	2009-05-14 13:18:33 UTC (rev 9752)
@@ -49,47 +49,54 @@
 }
 
 // demo for continuation support
-function continuation(req) {
+function continuation(req, run) {
 
-    // local data - this is the data that is shared between continuations of this function
-    var data = {};
-    var session = new ContinuationSession("welcome", "ask_name", "ask_food", "ask_animal", "result");
-    req = session.start();
+    if (!run) {
+        return SkinnedResponse('skins/continuation.txt', {
+            page: "welcome",
+            title: "Continuations"
+        });
+    }
 
-    // render intro page
-    req = session.step(1).render(SkinnedResponse('skins/continuation.txt', {
-        session: session,
-        title: "Continuations",
-        data: data
-    }));
-    
-    req = session.step(2).render(SkinnedResponse('skins/continuation.txt', {
-        session: session,
-        title: "Question 1",
-        data: data
-    }));
-    if (req.isPost)
-        data.name = req.params.name;
+    var session = new ContinuationSession(req);
 
-    req = session.step(3).render(SkinnedResponse('skins/continuation.txt', {
-        session: session,
-        title: "Question 2",
-        data: data
-    }));
-    if (req.isPost)
-        data.food = req.params.food;
+    session.addPage("ask_name", function(req) {
+        return SkinnedResponse('skins/continuation.txt', {
+            session: session,
+            page: session.page,
+            title: "Question 1"
+        })
+    });
 
-    req = session.step(4).render(SkinnedResponse('skins/continuation.txt', {
-        session: session,
-        title: "Question 3",
-        data: data
-    }));
-    if (req.isPost)
-        data.animal = req.params.animal;
+    session.addPage("ask_food", function(req) {
+        if (req.isPost)
+            session.data.name = req.params.name;
+        return SkinnedResponse('skins/continuation.txt', {
+            session: session,
+            page: session.page,
+            title: "Question 2"
+        });
+    });
 
-    session.step(5).render(SkinnedResponse('skins/continuation.txt', {
-        session: session,
-        title: "Thank you!",
-        data: data
-    }));
+    session.addPage("ask_animal", function(req) {
+        if (req.isPost)
+            session.data.food = req.params.food;
+        return SkinnedResponse('skins/continuation.txt', {
+            session: session,
+            page: session.page,
+            title: "Question 3"
+        });
+    });
+
+    session.addPage("result", function(req) {
+        if (req.isPost)
+            session.data.animal = req.params.animal;
+        return SkinnedResponse('skins/continuation.txt', {
+            session: session,
+            page: session.page,
+            title: "Thank you!"
+        });
+    });
+
+    return session.run();
 }

Modified: helma-ng/trunk/apps/demo/config.js
===================================================================
--- helma-ng/trunk/apps/demo/config.js	2009-05-14 13:18:26 UTC (rev 9751)
+++ helma-ng/trunk/apps/demo/config.js	2009-05-14 13:18:33 UTC (rev 9752)
@@ -9,7 +9,6 @@
 ];
 
 exports.middleware = [
-    'helma/webapp/continuation',
     'helma/logging',
 ];
 

Modified: helma-ng/trunk/apps/demo/skins/continuation.txt
===================================================================
--- helma-ng/trunk/apps/demo/skins/continuation.txt	2009-05-14 13:18:26 UTC (rev 9751)
+++ helma-ng/trunk/apps/demo/skins/continuation.txt	2009-05-14 13:18:33 UTC (rev 9752)
@@ -1,72 +1,73 @@
 <% extends 'base.html' %>
 
 <% subskin 'content' -------------------------------------------------------------------- %>
-<% render <% session.page %> %>
+<% render <% page %> %>
 
-<% subskin 'welcome' %>
+<% subskin 'welcome' | markdown %>
 
-    <p>This is a little game to show off continuations support in Helma NG.
-    All you'll have to do is answer a few easy questions.</p>
+  This is a little game to show off continuations support in Helma NG.
+  All you'll have to do is answer a few easy questions.</p>
 
-    <p><a href="<% session.forward %>">Click here to start.</a></p>
+  [Click here to start](/continuation/run)
 
-    <p>What's special about this demo is that all pages are generated by
-    <a href="http://dev.helma.org/trac/helma/browser/helma-ng/trunk/apps/demo/actions.js#L44">one
-    single JavaScript function</a> which is run only once, but resumed and suspended
-    earch time a page is rendered. Local variables are stored inside the
-    function when it is supsended, so no extra effort is required to 
-    manage state between pages.</p>
+  What's special about this demo is that all pages are generated by
+  [one single JavaScript function][1] providing multiple action callbacks. 
+  Local variables are stored in the HTTP user session, so no extra effort is required to
+  manage state between pages.</p>
 
-    <% render 'list' %>
+  [1]: http://dev.helma.org/trac/helma/browser/helma-ng/trunk/apps/demo/actions.js#L44
 
-<% subskin 'ask_name' %>
+<% subskin 'ask_name' | markdown %>
 
-    What is your name? <form method="post" action="<% session.forward %>">
-    <input name="name" value="<% data.name %>">
-    <input type="submit">
-    </form>
+  What is your name?
+  <form method="post" action="<% session.forward %>">
+  <input name="name" value="<% session.data.name %>">
+  <input type="submit">
+  </form>
 
-    <p><a href="<% session.back %>">back</a> <a href="<% session.forward %>">forward</a></p>
+  [forward](<% session.forward %>)
 
-    <% render 'list' %>
+  <% render 'list' %>
 
-<% subskin 'ask_food' %>
+<% subskin 'ask_food' | markdown %>
 
-    What is your favorite food? <form method="post" action="<% session.forward %>">
-    <input name="food" value="<% data.food %>">
-    <input type="submit">
-    </form>
+  What is your favorite food?
+  <form method="post" action="<% session.forward %>">
+  <input name="food" value="<% session.data.food %>">
+  <input type="submit">
+  </form>
 
-    <p><a href="<% session.back %>">back</a> <a href="<% session.forward %>">forward</a></p>
+  [back](<% session.back %>) [forward](<% session.forward %>)
 
-    <% render 'list' %>
+  <% render 'list' %>
 
-<% subskin 'ask_animal' %>
+<% subskin 'ask_animal' | markdown %>
 
-    What is your favorite animal? <form method="post" action="<% session.forward %>">
-    <input name="animal" value="<% data.animal %>">
-    <input type="submit">
-    </form>
+  What is your favorite animal?
+  <form method="post" action="<% session.forward %>">
+  <input name="animal" value="<% session.data.animal %>">
+  <input type="submit">
+  </form>
 
-    <p><a href="<% session.back %>">back</a> <a href="<% session.forward %>">forward</a></p>
+  [back](<% session.back %>) [forward](<% session.forward %>)
 
-    <% render 'list' %>
+  <% render 'list' %>
 
-<% subskin 'result' %>
+<% subskin 'result' | markdown %>
 
-    Thanks for your collaboration. Your name is <b><% data.name %></b>,
-    you love to eat <b><% data.food %></b> and your favorite animal
-    is <b><% data.animal %></b>.
+  Thanks for your collaboration. Your name is <b><% session.data.name %></b>,
+  you love to eat <b><% session.data.food %></b> and your favorite animal
+  is <b><% session.data.animal %></b>.
 
-    <p><a href="<% session.back %>">back</a></p>
+  [back](<% session.back %>)
 
-    <% render 'list' %>
+  <% render 'list' %>
 
 <% subskin 'list'%>
 
-    <% for value in <% data %> render 'item'
+  <% for value in <% session.data %> render 'item'
         | wrap "<div style='color: gray;'>" "</div>"
-    %>
+  %>
 
 <% subskin 'item' %>
    <div><% index %>: <% value %></div>

Modified: helma-ng/trunk/modules/helma/webapp/continuation.js
===================================================================
--- helma-ng/trunk/modules/helma/webapp/continuation.js	2009-05-14 13:18:26 UTC (rev 9751)
+++ helma-ng/trunk/modules/helma/webapp/continuation.js	2009-05-14 13:18:33 UTC (rev 9752)
@@ -2,132 +2,83 @@
  * Continuation support for Helma NG
  */
 
-export('ContinuationSession', 'handleRequest');
+export('ContinuationSession');
 
 var system = require('helma/system');
 var log = require('helma/logging').getLogger(__name__);
+importClass(java.util.HashMap);
 
-var __shared__ = true;
+function ContinuationSession(req) {
 
-function ContinuationSession() {
+    var id = req.params._cid;
+
+    if (!id) {
+        throw {redirect: getContinuationUrl(0)};
+    }
+
+    var data = getData(req) || new HashMap();
     var pages = [];
-    var ids = [];
-    var currentstep = 0;
-    for (var i = 0; i < arguments.length; i++) {
-        pages.push(arguments[i]);
+    var callbacks = [];
+    var length = 0;
+    var step = parseInt(req.params._cstep) || 0;
+
+    this.addPage = function(name, callback) {
+        pages.push(name);
+        callbacks.push(callback);
+        length = pages.length;
     }
-    var length = pages.length;
 
-    this.start = function() {
-        var req = require('helma/webapp/env').req;
-        if (!req.params.helma_continuation) {
-            // set query param so helma knows to switch rhino optimization level to -1
-            throw { redirect: getContinuationUrl(0) };
+    this.run = function() {
+        var callback = callbacks[step];
+        if (!(typeof callback === "function")) {
+            throw new Error("invalid continuation step: " + step);
         }
-        if (system.getOptimizationLevel() > -1) {
-            system.setOptimizationLevel(-1);
-            throw { retry: true };
-        }
-        currentstep = 0;
-        ids[0] = id = req.params.helma_continuation;
-        var continuation = createContinuation();
-        if (continuation instanceof Continuation) {
-            log.debug("Recording continuation start: " + id);
-            setCallback(req, id, continuation);
-            return req;
-        } else {
-            return continuation;
-        }
+        var result = callback(req);
+        setData(req, id, data);
+        return result;
     };
 
-    this.step = function(step) {
-        currentstep = step - 1;
-        return this;
-    };
+    Object.defineProperty(this, "data", {
+        value: new ScriptableMap(data)
+    });
 
-    this.render = function(res) {
-        var req = require('helma/webapp/env').req;
-        // capture continuation and store it in callback container
-        var id = getContinuationId(currentstep + 1);
-        var continuation = createContinuation();
-        if (continuation instanceof Continuation) {
-            if (!getCallback(req, id))
-                setCallback(req, id, continuation);
-            // Exit current js context by calling empty continuation with return value
-            new org.mozilla.javascript.NativeContinuation()(res);
-        } else {
-            return continuation;
-        }        
-    };
+    Object.defineProperty(this, "page", {
+        get: function() pages[step]
+    });
 
-    this.page = function() {
-        return pages[currentstep];
-    };
+    Object.defineProperty(this, "step", {
+        get: function() step,
+        set: function(s) { step = s; }
+    })
 
     this.back = function() {
-        return currentstep > 0 ? getContinuationUrl(currentstep - 1) : null;
+        return step > 0 ? getContinuationUrl(step - 1) : null;
     };
 
     this.forward = function() {
-        return currentstep < length ? getContinuationUrl(currentstep + 1) : null;
+        return step < length ? getContinuationUrl(step + 1) : null;
     };
 
     function getContinuationUrl(step) {
-        var req = require('helma/webapp/env').req;
-        return req.path + "?helma_continuation=" + getContinuationId(step);
+        id = id || generateId();
+        return req.path + "?_cid=" + id + "&_cstep=" + String(step);
     }
 
-    function getContinuationId(step) {
-        if (ids[step] == null) {
-            ids[step] = generateId();
-        }
-        return ids[step];
-    }
-
-    function createContinuation() {
-        return new Continuation();
-    }
-
     function generateId() {
         return Math.ceil(Math.random() * Math.pow(2, 32)).toString(36);
     }
 }
 
-/**
- * Continuation middleware function
- * @param req the request
- * @return the continuation result
- */
-function handleRequest(req) {
-    var id = req.params.helma_continuation;
-    var continuation = getCallback(req, id);
-    if (continuation) {
-        if (system.getOptimizationLevel() > -1) {
-            system.setOptimizationLevel(-1);
-            throw { retry: true };
-        }        
-        log.debug("resuming continuation " + id + " with req " + req);
-        return continuation(req);
-    }
-    return req.process();
-}
-
-var setCallback = function(req, id, func) {
-    if (!req.session.data.continuation) {
-        req.session.data.continuation = {};
-    }
-    log.debug("Registered continuation: " + id);
-    req.session.data.continuation[id] = func;
-    // fauxsessions[id] = func;
+var setData = function(req, id, data) {
+    log.debug("Setting continuation data: " + id);
+    req.session.data[id] = data;
 };
 
-var getCallback = function(req, id) {
-    if (!req.session.data.continuation || !req.session.data.continuation[id]) {
+var getData = function(req, id) {
+    id = id || req.params._cid;
+    if (!id) {
         return null;
     }
-    return req.session.data.continuation[id];
-    // return fauxsessions[id];
+    return req.session.data[id];
 };
 
-// awful hack to make this work on google app engine
-// var fauxsessions = {}