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

[email protected] Wed, 22 Apr 2009 01:41:53 +0200 (CEST)
Newsgroups gmane.comp.java.helma.cvs
Message-ID <20090421234153.5BC9B3D0D6@mia>
Author: hannes
Date: 2009-04-22 01:41:53 +0200 (Wed, 22 Apr 2009)
New Revision: 9647

Modified:
   helma-ng/trunk/apps/demo/actions.js
   helma-ng/trunk/apps/demo/config.js
   helma-ng/trunk/apps/demo/skins/continuation.html
   helma-ng/trunk/modules/helma/webapp/continuation.js
Log:
Rewrite continuation support and fix continuation demo app.

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

Modified: helma-ng/trunk/apps/demo/actions.js
===================================================================
--- helma-ng/trunk/apps/demo/actions.js	2009-04-21 23:41:51 UTC (rev 9646)
+++ helma-ng/trunk/apps/demo/actions.js	2009-04-21 23:41:53 UTC (rev 9647)
@@ -1,5 +1,6 @@
 import('helma/logging');
 include('helma/webapp/response');
+include('helma/webapp/continuation');
 
 var log = helma.logging.getLogger(__name__);
 
@@ -41,72 +42,53 @@
 }
 
 // demo for continuation support
-function continuation(req, res) {
+function continuation(req) {
 
-    return new SkinnedResponse('skins/continuation.html', {
-        title: "Continuations",
-        skin: "start",
-        note: "NOTE: Continuation support is currently broken, so I have disabled this demo for the time being."
-    });
-
-    // local data - this is the data that is shared between resuming and suspension
+    // local data - this is the data that is shared between continuations of this function
     var data = {};
-    var pages = ["start", "name", "favorite food", "favorite animal", "result"];
-    // to have only one continuation per user just give the pages fixed ids
-    // var pageIds = [0, 1, 2, 3, 4];
-    // to have continuations created dynamically start with empty page ids
-    var pageIds = [];
 
-    // mark start of continuation code. We never step back earlier than this
-    // otherwise local data would be re-initialized
-    pageIds[0] = Continuation.startId(req);
-    [req, res] = Continuation.markStart(req, res, pageIds[0]);
+    req = ContinuationMark(req, "start");
+
     // render intro page
-    renderPage(0);
-    // render first page
-    renderPage(1)
-    // render second page
-    renderPage(2);
-    // render third page
-    renderPage(3);
-    // render overview page
-    if (!data.name) renderPage(1);
-    renderPage(4);
+    req = ContinuationRequest(req, SkinnedResponse('skins/continuation.html', {
+        skin: "start",
+        title: "Continuations Demo",
+        data: data,
+        forward: ContinuationUrl("name")
+    }), "name");
+    
+    req = ContinuationRequest(req, SkinnedResponse('skins/continuation.html', {
+        skin: "name",
+        title: "Question 1",
+        data: data,
+        back: ContinuationUrl("start"),
+        forward: ContinuationUrl("food")
+    }), "food");
+    data.name = req.params.name || data.name;
 
-    // the local function to do the actual work
-    function renderPage(id) {
-        var previous = pages[id - 1]
-        if (req.isPost && previous) {
-           data[previous] = req.params[previous];
-        }
-        if (id < pages.length - 1) {
-            pageIds[id + 1] = Continuation.nextId(req, pageIds[id + 1]);
-            if (id < 1) {
-                res.render('skins/continuation.html', {
-                    title: "Welcome",
-                    skin: "start",
-                    data: data,
-                    forward: Continuation.getUrl(req, pageIds[id + 1])
-                });
-            } else {
-                res.render('skins/continuation.html', {
-                    title: "Question " + id,
-                    skin: "mask",
-                    input: pages[id],
-                    data: data,
-                    value: data[pages[id]],
-                    back: Continuation.getUrl(req, pageIds[id - 1]),
-                    forward: Continuation.getUrl(req, pageIds[id + 1])
-                });
-            }
-            [req, res] = Continuation.nextPage(req, pageIds[id + 1]);
-        } else {
-            res.render('skins/continuation.html', {
-                title: "Thanks!",
-                skin: "result",
-                data: data,
-                back: Continuation.getUrl(req, pageIds[id - 1])
-            });
-        }
-    }
+    req = ContinuationRequest(req, SkinnedResponse('skins/continuation.html', {
+        skin: "food",
+        title: "Question 2",
+        data: data,
+        back: ContinuationUrl("name"),
+        forward: ContinuationUrl("animal")
+    }), "animal");
+    data.food = req.params.food || data.food;
+
+    req = ContinuationRequest(req, SkinnedResponse('skins/continuation.html', {
+        skin: "animal",
+        title: "Question 3",
+        data: data,
+        back: ContinuationUrl("food"),
+        forward: ContinuationUrl("result")
+    }), "result");
+    data.animal = req.params.animal || data.animal;
+
+    return SkinnedResponse('skins/continuation.html', {
+        skin: "result",
+        title: "Thank you!",
+        data: data,
+        back: ContinuationUrl("animal")
+    });
+
 }
\ No newline at end of file

Modified: helma-ng/trunk/apps/demo/config.js
===================================================================
--- helma-ng/trunk/apps/demo/config.js	2009-04-21 23:41:51 UTC (rev 9646)
+++ helma-ng/trunk/apps/demo/config.js	2009-04-21 23:41:53 UTC (rev 9647)
@@ -8,9 +8,9 @@
 ];
 
 exports.middleware = [
-    // 'helma/webapp/continuation',
+    'helma/webapp/continuation',
+    // 'helma/profiler',
     'helma/logging',
-    'helma/profiler'
 ];
 
 exports.charset = 'utf8';

Modified: helma-ng/trunk/apps/demo/skins/continuation.html
===================================================================
--- helma-ng/trunk/apps/demo/skins/continuation.html	2009-04-21 23:41:51 UTC (rev 9646)
+++ helma-ng/trunk/apps/demo/skins/continuation.html	2009-04-21 23:41:53 UTC (rev 9647)
@@ -20,10 +20,10 @@
 
     <% render 'list' %>
 
-<% subskin 'mask' %>
+<% subskin 'name' %>
 
-    What is your <% input %>? <form method="post" action="<% forward %>">
-    <input name="<% input %>" value="<% value %>">
+    What is your name? <form method="post" action="<% forward %>">
+    <input name="name" value="<% data.name %>">
     <input type="submit">
     </form>
 
@@ -31,11 +31,33 @@
 
     <% render 'list' %>
 
+<% subskin 'food' %>
+
+    What is your favorite food? <form method="post" action="<% forward %>">
+    <input name="food" value="<% data.food %>">
+    <input type="submit">
+    </form>
+
+    <p><a href="<% back %>">back</a> <a href="<% forward %>">forward</a></p>
+
+    <% render 'list' %>
+
+<% subskin 'animal' %>
+
+    What is your favorite animal? <form method="post" action="<% forward %>">
+    <input name="animal" value="<% data.animal %>">
+    <input type="submit">
+    </form>
+
+    <p><a href="<% back %>">back</a> <a href="<% forward %>">forward</a></p>
+
+    <% render 'list' %>
+
 <% subskin 'result' %>
 
-    Thanks for your collaboration. Your name is <% data.name %>,
-    you love to eat <% "data.favorite food" %> and your favorite animal
-    is <% "data.favorite animal" %>.
+    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>.
 
     <p><a href="<% back %>">back</a></p>
 

Modified: helma-ng/trunk/modules/helma/webapp/continuation.js
===================================================================
--- helma-ng/trunk/modules/helma/webapp/continuation.js	2009-04-21 23:41:51 UTC (rev 9646)
+++ helma-ng/trunk/modules/helma/webapp/continuation.js	2009-04-21 23:41:53 UTC (rev 9647)
@@ -1,152 +1,78 @@
 /**
- * Continuation support for Helma. You need to run Rhino in interpreter mode
- * (rhino.optlevel = -1) for Continuations to work.
- *
- * This framework adds three static methods to the Continuation constructor:
- *
- * <ul>
- *   <li>Continuation.nextUrl()</li>
- *   <li>Continuation.nextPage()</li>
- * </ul>
- *
- * Example usage:
- *
- * <pre><code>
- *    function continuation_action() {
- *        res.write('<form method="post" action="' + Continuation.nextUrl() + '">\
- *                    <input name="foo"/>\
- *                    <input type="submit"/>\
- *                   </form>');
- *        Continuation.nextPage();
- *        var foo = req.params.foo;
- *        res.write('<a href="' + Continuation.nextUrl() + '">click here</a>');
- *        Continuation.nextPage();
- *        res.write("you said: " + foo);
- *    }
- * </code></pre>
- *
+ * Continuation support for Helma NG
  */
 
-import('helma/system', 'system');
+include('helma/webapp/response');
+var system = require('helma/system');
 
-export('resume');
+export('handleRequest', "ContinuationMark", "ContinuationRequest", "ContinuationUrl", "ContinuationId");
 
-
 var log = require('helma/logging').getLogger(__name__);
-var continuation_id = null;
 
+var ids = {};
 
-function onRequest(req) {
-    if (req.params.helma_continuation) {
-        system.setRhinoOptimizationLevel(-1);
-    }
+function ContinuationUrl(key) {
+    return "?helma_continuation=" + ContinuationId(key);
 }
 
-
-/**
- * Get the id for the next continuation, suitable for GET forms where
- * the id has to be set via hidden input field.
- * @param id the continuation id. If not given a new id is generated.
- * @return the continuation url
- */
-Continuation.nextId = function(req, id) {
-    id = getId(req, id);
-    continuation_id = id;
-    return continuation_id;
-};
-
-/**
- * Convenience method that returns the URL for the next continuation,
- * built from the current URL with an added continuation_id parameter.
- * Suitable for POST forms and links.
- * @param id the continuation id. If not given a new id is generated.
- * @return the continuation url
- */
-Continuation.nextUrl = function(req, id) {
-    id = getId(req, id);
-    continuation_id = id;
-    return Continuation.getUrl(req, id);
-};
-
-/**
- * Get the url for a continuation with the given id.
- * @param id the continuation id. If not given a new id is generated.
- * @return the continuation url
- */
-Continuation.getUrl = function(req, id) {
-   return req.path + "?helma_continuation=" + getId(req, id);
+function ContinuationId(key) {
+    if (!key)
+        return generateId();
+    if (!(key in ids))
+        ids[key] = generateId();
+    return ids[key];
 }
 
 /**
  * Stop current execution and register continuation for later resumption.
  * @param id the continuation id. If not given a new id is generated.
  */
-Continuation.nextPage = function(req, id) {
+function ContinuationRequest(req, res, key) {
     // capture continuation and store it in callback container
-    id = getId(req, id);
+    var id = ContinuationId(key);
+    log.info("registering callback for id " + id);
     setCallback(req, id, new Continuation());
     // trick to exit current context: call empty continuation
-    new org.mozilla.javascript.NativeContinuation()();
+    new org.mozilla.javascript.NativeContinuation()(res);
 };
 
-Continuation.startId = function(req, id) {
-    id = req.params.helma_continuation || id;
-    return id;    
+function ContinuationMark(req, key) {
+    var id = req.params.helma_continuation;
+    ids[key] = id;
+    var cont = new Continuation();
+    log.info("Recording continuation start: " + id);
+    setCallback(req, id, cont);
+    return req;
 }
 
 /**
- * This is a utility method used at the start of a continuation action.
- * If the current request does not have a continuation id, it is redirected
- * to a request containing one. If the request does have a continuation id,
- * the current state is registered as continuation start marker. This can be
- * used to avoid re-executing earlier code containing definition of local
- * variables.
- * @param id the continuation id. If not given a new id is generated.
+ * Continuation middleware function
+ * @param req the request
  * @return the continuation result
  */
-Continuation.markStart = function(req, res, id) {
+function handleRequest(req) {
+    if (system.getOptimizationLevel() > -1) {
+        system.setOptimizationLevel(-1);
+        throw { retry: true };
+    }
     if (!req.params.helma_continuation) {
         // set query param so helma knows to switch rhino optimization level to -1
-        res.redirect(Continuation.nextUrl(req, id));
-    } else {
-        id = req.params.helma_continuation;
-        var cont = new Continuation();
-        log.info("Recording continuation start: " + id);
-        setCallback(req, id, cont);
-        continuation_id = null;
-        cont([req, res]);
+        throw { redirect: ContinuationUrl() };
     }
-}
 
-/**
- * Register current state with the given id but don't exit execution context
- * @param id the continuation id. If not given a new id is generated.
- * @return the continuation id
- */
-Continuation.registerPage = function(req, id) {
-    id = getId(req, id);
-    setCallback(req, id, new Continuation());
-    return Continuation.getUrl(req, id); 
+    var id = req.params.helma_continuation;
+    var continuation = getCallback(req, id);
+    if (continuation) {
+        log.info("resuming continuation " + id + " with req " + req);
+        return continuation(req);
+    }
+    return req.process();
 }
 
-// Private helper functions
-
-var getId = function(req, id) {
-   if (id == null) {
-      return continuation_id || generateId(req);
-   }
-   return (String(id));
+function generateId() {
+    return Math.ceil(Math.random() * Math.pow(2, 32)).toString(36);
 }
 
-var generateId = function(req) {
-    var id;
-    do {
-        id = Math.ceil(Math.random() * Math.pow(2, 64)).toString(36);
-    } while (getCallback(req, id));
-    log.debug("Generated continuation id: " + id);
-    return id;
-}
-
 var setCallback = function(req, id, func) {
     if (!req.session.data.continuation) {
         req.session.data.continuation = {};
@@ -161,26 +87,3 @@
     }
     return req.session.data.continuation[id];
 };
-
-/**
- * Check if there is a helma_continuation http parameter, and if so,
- * check if there is a matching continuation, and if so, invoke the continuation
- * and return null.
- */
-var resume = function(req, res) {
-    var continuationId = req.params.helma_continuation;
-    if (continuationId && req.session.data.continuation) {
-        var continuation = req.session.data.continuation[continuationId];
-        if (continuation) {
-            log.debug("Resuming continuation " + continuationId);
-            try {
-                continuation([req, res]);
-                return true;
-            } catch (e) {
-                error(e);
-                return true;
-            }
-        }
-    }
-    return false;
-}