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;
-}