aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/framework-src
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/framework-src')
-rw-r--r--trunk/infrastructure/framework-src/modules/atomfeed.js72
-rw-r--r--trunk/infrastructure/framework-src/modules/blob.js50
-rw-r--r--trunk/infrastructure/framework-src/modules/cache_utils.js36
-rw-r--r--trunk/infrastructure/framework-src/modules/comet.js91
-rw-r--r--trunk/infrastructure/framework-src/modules/dateutils.js48
-rw-r--r--trunk/infrastructure/framework-src/modules/dispatch.js149
-rw-r--r--trunk/infrastructure/framework-src/modules/ejs.js471
-rw-r--r--trunk/infrastructure/framework-src/modules/email.js53
-rw-r--r--trunk/infrastructure/framework-src/modules/exceptionutils.js210
-rw-r--r--trunk/infrastructure/framework-src/modules/execution.js58
-rw-r--r--trunk/infrastructure/framework-src/modules/fastJSON.js27
-rw-r--r--trunk/infrastructure/framework-src/modules/faststatic.js318
-rw-r--r--trunk/infrastructure/framework-src/modules/fileutils.js108
-rw-r--r--trunk/infrastructure/framework-src/modules/funhtml.js158
-rw-r--r--trunk/infrastructure/framework-src/modules/global/appjet.js107
-rw-r--r--trunk/infrastructure/framework-src/modules/global/request.js312
-rw-r--r--trunk/infrastructure/framework-src/modules/global/response.js294
-rw-r--r--trunk/infrastructure/framework-src/modules/image.js110
-rw-r--r--trunk/infrastructure/framework-src/modules/jsutils.js195
-rw-r--r--trunk/infrastructure/framework-src/modules/netutils.js88
-rw-r--r--trunk/infrastructure/framework-src/modules/profiler.js48
-rw-r--r--trunk/infrastructure/framework-src/modules/sessions.js156
-rw-r--r--trunk/infrastructure/framework-src/modules/sqlbase/persistent_vars.js57
-rw-r--r--trunk/infrastructure/framework-src/modules/sqlbase/sqlbase.js205
-rw-r--r--trunk/infrastructure/framework-src/modules/sqlbase/sqlcommon.js99
-rw-r--r--trunk/infrastructure/framework-src/modules/sqlbase/sqlobj.js505
-rw-r--r--trunk/infrastructure/framework-src/modules/stringutils.js399
-rw-r--r--trunk/infrastructure/framework-src/modules/sync.js81
-rw-r--r--trunk/infrastructure/framework-src/modules/timer.js29
-rw-r--r--trunk/infrastructure/framework-src/modules/varz.js52
-rw-r--r--trunk/infrastructure/framework-src/modules/yuicompressor.js85
-rw-r--r--trunk/infrastructure/framework-src/oncomet.js38
-rw-r--r--trunk/infrastructure/framework-src/onerror.js24
-rw-r--r--trunk/infrastructure/framework-src/onprint.js19
-rw-r--r--trunk/infrastructure/framework-src/onrequest.js24
-rw-r--r--trunk/infrastructure/framework-src/onreset.js19
-rw-r--r--trunk/infrastructure/framework-src/onsars.js27
-rw-r--r--trunk/infrastructure/framework-src/onscheduledtask.js33
-rw-r--r--trunk/infrastructure/framework-src/onshutdown.js19
-rw-r--r--trunk/infrastructure/framework-src/onstartup.js19
-rw-r--r--trunk/infrastructure/framework-src/onsyntaxerror.js17
-rw-r--r--trunk/infrastructure/framework-src/postamble.js19
-rw-r--r--trunk/infrastructure/framework-src/preamble.js325
-rw-r--r--trunk/infrastructure/framework-src/syntaxerror.js32
44 files changed, 5286 insertions, 0 deletions
diff --git a/trunk/infrastructure/framework-src/modules/atomfeed.js b/trunk/infrastructure/framework-src/modules/atomfeed.js
new file mode 100644
index 0000000..4b86eeb
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/atomfeed.js
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("stringutils.sprintf");
+
+// TODO: validate XHTML of entries?
+
+function _xmlDate(d) {
+ return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
+ d.getUTCFullYear(), d.getUTCMonth()+1, d.getUTCDate(),
+ d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds());
+}
+
+// "entries" is an object with "forEach" member (an Array works).
+// Each entry should have these properties:
+// * title
+// * author
+// * published (Date)
+// * updated (Date)
+// * href (URL for HTML version)
+// * content (valid xhtml)
+//
+// NOTE: entries should be sorted descending by entry.updated (newest first)
+//
+
+function renderFeed(title, lastUpdated, entries, href) {
+ function ampesc(url) {
+ return url.replace(/&/g, '&');
+ }
+
+ var r = [];
+ r.push('<?xml version="1.0" encoding="utf-8"?>',
+ '<feed xmlns="http://www.w3.org/2005/Atom">');
+
+ r.push('<title type="text">' + title + '</title>');
+ r.push('<updated>' + _xmlDate(lastUpdated) + '</updated>');
+ r.push('<link rel="self" href="' + request.url + '" />');
+ r.push('<link rel="alternate" type="text/html" href="' + href + '" />');
+ r.push('<id>' + ampesc(request.url) + '</id>');
+
+ entries.forEach(function(entry) {
+ r.push('<entry>',
+ '<title>' + entry.title + '</title>',
+ '<author><name>' + entry.author + '</name></author>',
+ '<published>' + _xmlDate(entry.published) + '</published>',
+ '<updated>' + _xmlDate(entry.updated) + '</updated>',
+ '<link rel="alternate" type="text/html" href="' + entry.href + '" />',
+ '<id>'+ampesc(entry.href)+'</id>',
+ '<content type="xhtml">',
+ '<div xmlns="http://www.w3.org/1999/xhtml">'+entry.content+'</div>',
+ '</content>',
+ '</entry>');
+ });
+
+ r.push('</feed>');
+
+ return r.join('\n');
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/blob.js b/trunk/infrastructure/framework-src/modules/blob.js
new file mode 100644
index 0000000..af788a0
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/blob.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+/**
+ * Constructs and returns a new Blob from a JavaScript string.
+ */
+function stringToBlob(contentType, string) {
+ return { contentType: contentType,
+ _stringData: string,
+ numDataBytes: string.length*2 };
+}
+
+/**
+ * Constructs and returns a new Blob from a Java byte array (byte[]).
+ */
+function byteArrayToBlob(contentType, javaByteArray) {
+ return { contentType: contentType,
+ _binaryData: javaByteArray,
+ numDataBytes: javaByteArray.length };
+}
+
+/**
+ * Serves a Blob to the client, using the appropriate content-type,
+ * and stops execution of the current request.
+ */
+function serveBlob(blob) {
+ response.setContentType(blob.contentType);
+ if (blob._binaryData) {
+ response.writeBytes(new java.lang.String(blob._binaryData, 0));
+ }
+ else if (blob._stringData) {
+ response.write(blob._stringData);
+ }
+ response.stop();
+}
diff --git a/trunk/infrastructure/framework-src/modules/cache_utils.js b/trunk/infrastructure/framework-src/modules/cache_utils.js
new file mode 100644
index 0000000..f2a360c
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/cache_utils.js
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("sync");
+
+/* performs function on an object from the cache, all within a lock */
+function syncedWithCache(name, fn) {
+ return sync.doWithStringLock("cache/"+name, function() {
+ var parts = name.split(".");
+ var obj = appjet.cache;
+ for (var i = 0; i < parts.length; i++) {
+ var p = parts[i];
+ if (!obj[p]) {
+ obj[p] = {};
+ }
+ obj = obj[p];
+ }
+ return fn(obj);
+ });
+}
+
+
+
diff --git a/trunk/infrastructure/framework-src/modules/comet.js b/trunk/infrastructure/framework-src/modules/comet.js
new file mode 100644
index 0000000..2331f8b
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/comet.js
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview
+ * Comet presents a real-time bidirectional-channel interface. Using comet, your
+ * server can push data to any connected client without waiting for that client
+ * to issue a request.
+ *
+ * <tt>comet</tt> reserves the <tt>/newcomet</tt> path and its subpaths for its
+ * own use.
+ */
+
+/**
+ * Gets a list of all client currently connected to the server.
+ * @function
+ * @name connections
+ * @return {array} An array of the string ids of all connected clients.
+ */
+function connections() {
+ return Packages.net.appjet.ajstdlib.Comet.connections(appjet.context);
+}
+
+function getNumCurrentConnections() {
+ return Packages.net.appjet.ajstdlib.Comet.getNumCurrentConnections();
+}
+
+function isConnected(id) {
+ return Packages.net.appjet.ajstdlib.Comet.isConnected(id);
+}
+
+function disconnect(id) {
+ Packages.net.appjet.ajstdlib.Comet.disconnect(id);
+}
+
+function getAttribute(id, key) {
+ var ret = Packages.net.appjet.ajstdlib.Comet.getAttribute(appjet.context, id, key);
+ if (ret != null)
+ return String(ret);
+}
+
+function setAttribute(id, key, value) {
+ Packages.net.appjet.ajstdlib.Comet.setAttribute(appjet.context, id, key, value);
+}
+
+/**
+ * Sends a message to a particular client.
+ * @functionn
+ * @name sendMessage
+ * @param {string} id The <tt>id</tt> of the client to send to.
+ * @param {string} data The string data to send to the client.
+ */
+function sendMessage(id, msg) {
+ Packages.net.appjet.ajstdlib.Comet.write(id, msg);
+}
+
+function headInclude() { return '<script src="'+appjet.config.cometPrefix+'/js/client.js"></script>'; };
+function clientCode() {
+ return Packages.net.appjet.ajstdlib.Comet.getClientCode(appjet.context);
+};
+function clientMTime() {
+ return Packages.net.appjet.ajstdlib.Comet.getClientMTime(appjet.context);
+};
+
+/**
+ * WebSocket allows the client to connect to the server via a
+ * "bidirectional" channel. Messages sent by the server are received by
+ * the client without the need for additional connections or other delays.
+ * @class
+ * @name WebSocket
+ * @param {string} id The id to use for this client.
+ */
+
+/**
+ * Connects to the server using the id specified in the constructor.
+ * @methodOf WebSocket
+ * @name connect
+ */
diff --git a/trunk/infrastructure/framework-src/modules/dateutils.js b/trunk/infrastructure/framework-src/modules/dateutils.js
new file mode 100644
index 0000000..72e87c8
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/dateutils.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function noon(date) {
+ return new Date(date.toString().split(' ').slice(0, 4).join(' ') + " 12:00");
+}
+
+function nextMonth(date) {
+ var newDate = new Date(date.getTime());
+ var newMonth = date.getMonth() + 1;
+ var newYear = date.getFullYear();
+ while (newMonth >= 12) {
+ newYear += 1;
+ newMonth -= 12;
+ }
+ newDate.setMonth(newMonth);
+ newDate.setFullYear(newYear);
+ return newDate;
+}
+
+var months =
+ ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"];
+
+var shortMonths = months.map(function(mon) { return mon.substr(0, 3); });
+
+var days =
+ ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
+
+var shortDays = days.map(function(day) { return day.substr(0, 3); });
+
+function dateFormat(date, format) {
+ var formatter = new Packages.java.text.SimpleDateFormat(format);
+ return String(formatter.format(date).toString());
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/modules/dispatch.js b/trunk/infrastructure/framework-src/modules/dispatch.js
new file mode 100644
index 0000000..e7e3ef0
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/dispatch.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview Dispatching for dynamic pages and static files rendered from disk.
+ */
+
+import("jsutils.eachProperty");
+import("stringutils");
+
+jimport("java.lang.System.out.println");
+
+//----------------------------------------------------------------
+// Util
+//----------------------------------------------------------------
+
+function PrefixMatcher(p) {
+ var rs = p.replace(/([\[\]\^\$\\\.\*\+\?\(\)\{\}\|])/g, "\\$1");
+ var r = new RegExp('^' + rs + '(.*)$');
+ return function(path) {
+ return r.exec(path);
+ }
+}
+
+// Like PrefixMatcher, but makes trailing '/' optional, as in /ep/admin or /ep/admin/.
+// If trailing '/' is omitted, will redirect to same path with trailing /.
+function DirMatcher(p) {
+ if (p.substr(-1) == '/') {
+ p = p.substr(0, p.length-1);
+ }
+ var prefixMatcher = PrefixMatcher(p+'/');
+ return function(path) {
+ if (path == p) {
+ response.redirect(p+'/');
+ }
+ return prefixMatcher(path);
+ }
+}
+
+function _pathMatches(p, loc) {
+ // returns a regex-result kind of array with length >= 1, or null
+ if (typeof(loc) == 'string') {
+ return (p == loc) ? [loc] : null;
+ }
+ if (typeof(loc) == 'function') {
+ return (loc(p) || null);
+ }
+ if (loc.exec) { // regexp
+ var r = loc.exec(p);
+ return r || null;
+ }
+ throw new Error('Uknown type of location: '+loc);
+}
+
+//----------------------------------------------------------------
+// Dispatcher
+//----------------------------------------------------------------
+
+var Dispatcher = function() {
+ this._routes = []; // Array([location, (local file path or function)])
+};
+
+Dispatcher.prototype.addLocations = function(l) {
+ var that = this;
+ l.forEach(function(x) { that._routes.push(x); });
+};
+
+Dispatcher.prototype.dispatch = function() {
+ var p = request.path;
+ var served = false;
+
+ for (var i = 0; (i < this._routes.length) && (served == false); i++) {
+ var loc = this._routes[i][0];
+ var dst = this._routes[i][1];
+
+ var match = _pathMatches(p, loc);
+ if (match) {
+ if (typeof(dst) != 'function') {
+ throw new Error('dispatch only dispatches to functions, and this is not a function: '+typeof(dst));
+ }
+
+ // call dst(group1, group2, group3, ...)
+ served = dst.apply(this, Array.prototype.slice.call(match, 1));
+ }
+ };
+
+ return served;
+};
+
+//----------------------------------------------------------------
+// fdisp
+//----------------------------------------------------------------
+
+function forward(module) {
+ return function(name) {
+ if (name === "") {
+ name = "main";
+ }
+ if (name) {
+ name = name.replace(/\-/g, '_');
+ }
+ var onreq = module['onRequest'];
+ var f = module['render_'+name];
+ var fg = module['render_'+name+'_get'];
+ var fp = module['render_'+name+'_post'];
+
+ var served = false;
+
+ if (onreq) {
+ served = onreq(name);
+ }
+
+ if (served) {
+ return true;
+ }
+
+ var method = request.method;
+ if (method == "HEAD") {
+ method = "GET";
+ }
+
+ if (f) {
+ f();
+ served = true;
+ } else if (method == "GET" && fg) {
+ fg();
+ served = true;
+ } else if (method == "POST" && fp) {
+ fp();
+ served = true;
+ }
+
+ return served;
+ };
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/ejs.js b/trunk/infrastructure/framework-src/modules/ejs.js
new file mode 100644
index 0000000..bf14ed3
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/ejs.js
@@ -0,0 +1,471 @@
+/*--------------------------------------------------------------------------
+ * EJS - Embedded JavaScript, version 0.1.0
+ * Copyright (c) 2007 Edward Benson
+ * http://www.edwardbenson.com/projects/ejs
+ * ------------------------------------------------------------------------
+ *
+ * EJS is freely distributable under the terms of an MIT-style license.
+ *
+ * EJS is a client-side preprocessing engine written in and for JavaScript.
+ * If you have used PHP, ASP, JSP, or ERB then you get the idea: code embedded
+ * in <% // Code here %> tags will be executed, and code embedded in <%= .. %>
+ * tags will be evaluated and appended to the output.
+ *
+ * This is essentially a direct JavaScript port of Masatoshi Seki's erb.rb
+ * from the Ruby Core, though it contains a subset of ERB's functionality.
+ *
+ * Requirements:
+ * prototype.js
+ *
+ * Usage:
+ * // source should be either a string or a DOM node whose innerHTML
+ * // contains EJB source.
+ * var source = "<% var ejb="EJB"; %><h1>Hello, <%= ejb %>!</h1>";
+ * var compiler = new EjsCompiler(source);
+ * compiler.compile();
+ * var output = eval(compiler.out);
+ * alert(output); // -> "<h1>Hello, EJB!</h1>"
+ *
+ * For a demo: see demo.html
+ * For the license: see license.txt
+ *
+ *--------------------------------------------------------------------------*/
+
+import("jsutils.*");
+import("funhtml");
+
+jimport("java.lang.System.out.println");
+jimport("net.appjet.ajstdlib.execution.executeCodeInNewScope");
+
+/* Make a split function like Ruby's: "abc".split(/b/) -> ['a', 'b', 'c'] */
+function rsplit(x, regex) {
+ var item = x;
+ var result = regex.exec(item);
+ var retArr = new Array();
+ while (result != null)
+ {
+ var first_idx = result.index;
+ var last_idx = regex.lastIndex;
+ if ((first_idx) != 0)
+ {
+ var first_bit = item.substring(0,first_idx);
+ retArr.push(item.substring(0,first_idx));
+ item = item.slice(first_idx);
+ }
+ retArr.push(result[0]);
+ item = item.slice(result[0].length);
+ result = regex.exec(item);
+ }
+ if (! item == '')
+ {
+ retArr.push(item);
+ }
+ return retArr;
+};
+
+/* Chop is nice to have too */
+function chop(x) {
+ return x.substr(0, x.length - 1);
+}
+
+/* Adaptation from the Scanner of erb.rb */
+var EjsScanner = function(source, left, right) {
+ this.left_delimiter = left +'%'; //<%
+ this.right_delimiter = '%'+right; //>
+ this.double_left = left+'%%';
+ this.double_right = '%%'+right;
+ this.left_equal = left+'%=';
+ this.left_comment = left+'%#';
+ if(left=='[') {
+ this.SplitRegexp = /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/;
+ }
+ else {
+ this.SplitRegexp = new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)')
+ }
+
+ this.source = source;
+ this.stag = null;
+ this.lines = 0;
+};
+EjsView = function(data) {
+ this.data = data;
+};
+EjsView.prototype.partial = function(options, data){
+ if(!data) data = this.data;
+ return new EJS(options).render(data);
+};
+
+EjsScanner.to_text = function(input){
+ if(input == null || input === undefined)
+ return '';
+ if(input instanceof Date)
+ return input.toDateString();
+ if(input.toString)
+ return input.toString();
+ return '';
+}
+
+EjsScanner.prototype = {
+
+ /* For each line, scan! */
+ scan: function(block) {
+ scanline = this.scanline;
+ regex = this.SplitRegexp;
+ if (! this.source == '')
+ {
+ var source_split = rsplit(this.source, /\n/);
+ for(var i=0; i<source_split.length; i++) {
+ var item = source_split[i];
+ this.scanline(item, regex, block);
+ }
+ }
+ },
+
+ /* For each token, block! */
+ scanline: function(line, regex, block) {
+ this.lines++;
+ var line_split = rsplit(line, regex);
+ for(var i=0; i<line_split.length; i++) {
+ var token = line_split[i];
+ if (token != null) {
+ try{
+ block(token, this);
+ }catch(e){
+ throw {type: 'EjsScanner', line: this.lines};
+ }
+ }
+ }
+ }
+};
+
+/* Adaptation from the Buffer of erb.rb */
+var EjsBuffer = function(pre_cmd, post_cmd) {
+ this.line = new Array();
+ this.script = "";
+ this.pre_cmd = pre_cmd;
+ this.post_cmd = post_cmd;
+
+ for (var i=0; i<this.pre_cmd.length; i++)
+ {
+ this.push(pre_cmd[i]);
+ }
+}
+EjsBuffer.prototype = {
+
+ push: function(cmd) {
+ this.line.push(cmd);
+ },
+
+ cr: function() {
+ this.script = this.script + this.line.join('; ');
+ this.line = new Array();
+ this.script = this.script + "\n";
+ },
+
+ close: function() {
+ if (this.line.length > 0)
+ {
+ for (var i=0; i<this.post_cmd.length; i++)
+ {
+ this.push(pre_cmd[i]);
+ }
+ this.script = this.script + this.line.join('; ');
+ line = null;
+ }
+ }
+
+};
+
+/* Adaptation from the Compiler of erb.rb */
+EjsCompiler = function(source, left) {
+ this.pre_cmd = ['var ___ejsO = "";'];
+ this.post_cmd = new Array();
+ this.source = ' ';
+ if (source != null)
+ {
+ if (typeof source == 'string')
+ {
+ source = source.replace(/\r\n/g, "\n");
+ source = source.replace(/\r/g, "\n");
+ this.source = source;
+ }
+ else if (source.innerHTML)
+ {
+ this.source = source.innerHTML;
+ }
+ if (typeof this.source != 'string')
+ {
+ this.source = "";
+ }
+ }
+ left = left || '<'
+ var right = '>'
+ switch(left) {
+ case '[':
+ right = ']'
+ break;
+ case '<':
+ break;
+ default:
+ throw left+' is not a supported deliminator'
+ break;
+ }
+ this.scanner = new EjsScanner(this.source, left, right);
+ this.out = '';
+}
+EjsCompiler.prototype = {
+ compile: function(options) {
+ options = options || {};
+ this.out = '';
+ var put_cmd = "___ejsO += ";
+ var insert_cmd = put_cmd;
+ var buff = new EjsBuffer(this.pre_cmd, this.post_cmd);
+ var content = '';
+ var clean = function(content)
+ {
+ content = content.replace(/\\/g, '\\\\');
+ content = content.replace(/\n/g, '\\n');
+ content = content.replace(/\"/g, '\\"');
+ return content;
+ };
+ this.scanner.scan(function(token, scanner) {
+ if (scanner.stag == null)
+ {
+ //alert(token+'|'+(token == "\n"))
+ switch(token) {
+ case '\n':
+ content = content + "\n";
+ buff.push(put_cmd + '"' + clean(content) + '";');
+ buff.cr();
+ content = '';
+ break;
+ case scanner.left_delimiter:
+ case scanner.left_equal:
+ case scanner.left_comment:
+ scanner.stag = token;
+ if (content.length > 0)
+ {
+ // Chould be content.dump in Ruby
+
+ buff.push(put_cmd + '"' + clean(content) + '"');
+ }
+ content = '';
+ break;
+ case scanner.double_left:
+ content = content + scanner.left_delimiter;
+ break;
+ default:
+ content = content + token;
+ break;
+ }
+ }
+ else {
+ switch(token) {
+ case scanner.right_delimiter:
+ switch(scanner.stag) {
+ case scanner.left_delimiter:
+ if (content[content.length - 1] == '\n')
+ {
+ content = chop(content);
+ buff.push(content);
+ buff.cr();
+ }
+ else {
+ buff.push(content);
+ }
+ break;
+ case scanner.left_equal:
+ buff.push(insert_cmd + "(EjsScanner.to_text(" + content + "))");
+ break;
+ }
+ scanner.stag = null;
+ content = '';
+ break;
+ case scanner.double_right:
+ content = content + scanner.right_delimiter;
+ break;
+ default:
+ content = content + token;
+ break;
+ }
+ }
+ });
+ if (content.length > 0)
+ {
+ // Chould be content.dump in Ruby
+ buff.push(put_cmd + '"' + clean(content) + '"');
+ }
+ buff.close();
+ this.out = buff.script + ";";
+ var to_be_evaled = [
+ 'var process = function(_CONTEXT,_VIEW) {',
+ ' with(_VIEW) {',
+ ' with (_CONTEXT) {',
+ this.out,
+ ' return ___ejsO;',
+ ' }',
+ ' }',
+ '};'
+ ].join('');
+ // make funhtml.* available in parent scope.
+ var parentScope = {};
+ parentScope.EjsScanner = EjsScanner;
+ keys(funhtml).forEach(function(k) {
+ parentScope[k] = funhtml[k];
+ });
+ var ret = executeCodeInNewScope(
+ parentScope,
+ to_be_evaled,
+ (options.name || "template"),
+ 1
+ );
+ this.process = ret.process;
+ }
+}
+
+
+//type, cache, folder
+EJS = function( options ){
+ this.set_options(options);
+
+ if(options.url){
+ var template = EJS.get(options.url, this.cache);
+ if (template) return template;
+ if (template == EJS.INVALID_PATH) return null;
+ this.text = EJS.request(options.url);
+ if(this.text == null){
+ //EJS.update(options.url, this.INVALID_PATH);
+ throw 'There is no template at '+options.url;
+ }
+ this.name = options.url;
+ }else if(options.element)
+ {
+ if(typeof options.element == 'string'){
+ var name = options.element;
+ options.element = document.getElementById( options.element );
+ if(options.element == null) throw name+'does not exist!';
+ }
+ if(options.element.value){
+ this.text = options.element.value;
+ }else{
+ this.text = options.element.innerHTML;
+ }
+ this.name = options.element.id;
+ this.type = '[';
+ }
+ var template = new EjsCompiler(this.text, this.type);
+
+ template.compile(options);
+
+
+ EJS.update(this.name, this);
+ this.template = template;
+};
+EJS.config = function(options){
+ EJS.cache = options.cache != null ? options.cache : EJS.cache;
+ EJS.type = options.type != null ? options.type : EJS.type;
+ var templates_directory = {}; //nice and private container
+
+ EJS.get = function(path, cache){
+ if(cache == false) return null;
+ if(templates_directory[path]) return templates_directory[path];
+ return null;
+ };
+
+ EJS.update = function(path, template) {
+ if(path == null) return;
+ templates_directory[path] = template;
+ };
+
+ EJS.INVALID_PATH = -1;
+
+
+};
+EJS.config( {cache: true, type: '<' } );
+
+EJS.prototype = {
+ render : function(object){
+ var v = new EjsView(object);
+ return this.template.process.call(v, object, v);
+ },
+ out : function(){
+ return this.template.out;
+ },
+ set_options : function(options){
+ this.type = options.type != null ? options.type : EJS.type;
+ this.cache = options.cache != null ? options.cache : EJS.cache;
+ this.text = options.text != null ? options.text : null;
+ this.name = options.name != null ? options.name : null;
+ },
+ // called without options, returns a function that takes the object
+ // called with options being a string, uses that as a url
+ // called with options as an object
+ update : function(element, options){
+ if(typeof element == 'string'){
+ element = document.getElementById(element);
+ }
+ if(options == null){
+ _template = this;
+ return function(object){
+ EJS.prototype.update.call(_template, element, object);
+ };
+ }
+ if(typeof options == 'string'){
+ params = {};
+ params.url = options;
+ _template = this;
+ params.onComplete = function(request){
+ var object = eval( request.responseText );
+ EJS.prototype.update.call(_template, element, object);
+ };
+ EJS.ajax_request(params);
+ }else
+ {
+ element.innerHTML = this.render(options);
+ }
+ }
+};
+
+ EJS.newRequest = function(){
+ var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
+ for(var i = 0; i < factories.length; i++) {
+ try {
+ var request = factories[i]();
+ if (request != null) return request;
+ }
+ catch(e) { continue;}
+ }
+ };
+
+ EJS.request = function(path){
+ var request = new EJS.newRequest();
+ request.open("GET", path, false);
+
+ try{request.send(null);}
+ catch(e){return null;}
+
+ if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
+
+ return request.responseText
+ };
+ EJS.ajax_request = function(params){
+ params.method = ( params.method ? params.method : 'GET');
+
+ var request = new EJS.newRequest();
+ request.onreadystatechange = function(){
+ if(request.readyState == 4){
+ if(request.status == 200){
+ params.onComplete(request);
+ }else
+ {
+ params.onComplete(request);
+ }
+ }
+ };
+ request.open(params.method, params.url);
+ request.send(null);
+ };
+
+//}
+
+
diff --git a/trunk/infrastructure/framework-src/modules/email.js b/trunk/infrastructure/framework-src/modules/email.js
new file mode 100644
index 0000000..2d81dc3
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/email.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// import("jsutils.eachProperty");
+//
+// function _paramObjectToParamArray(params, enc) {
+// var pa = [];
+// eachProperty(params, function(k, v) {
+// pa.push(enc ? encodeURIComponent(k.toString()) : k.toString());
+// pa.push(enc ? encodeURIComponent(v.toString()) : v.toString());
+// });
+// return pa;
+// }
+
+/**
+ * Simple way to send an email to a single recipient. Emails will have a
+ * "from" address of <code>noreply@{appjet.appName}.{appjet.mainDomain}</code>.
+ *
+ * Sending is limited to 100 emails per developer account per day. However,
+ * emails sent to the address on file for the app's owner are not counted
+ * toward this limit.
+ *
+ * @example
+result = sendEmail("noone@example.com", "Test Subject",
+ "Greetings!", {"Reply-To": "sender@example.com"});
+ *
+ * @param {strings} toAddress An array of email address strings, or a single string.
+ * @param {string} subject The message subject.
+ * @param {string} body The message body.
+ * @param {object} [headers] Optional headers to include in the
+ * message, as a dictionary of {name: value} entries.
+ */
+function sendEmail(toAddress, fromAddress, subject, headers, body) {
+ if (typeof(toAddress) == 'string')
+ toAddress = [toAddress];
+ var ret = Packages.net.appjet.ajstdlib.email.sendEmail(toAddress, fromAddress, subject, headers, body);
+ if (ret != "")
+ throw new Error(ret);
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/exceptionutils.js b/trunk/infrastructure/framework-src/modules/exceptionutils.js
new file mode 100644
index 0000000..b572a3a
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/exceptionutils.js
@@ -0,0 +1,210 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("ejs.EJS");
+import("funhtml.*");
+import("jsutils.{scalaF0,scalaF1}");
+import("stringutils.{toHTML,sprintf}");
+
+function _getException(ex) {
+ if (ex instanceof java.lang.Throwable) {
+ return new net.appjet.bodylock.JSRuntimeException(ex.getMessage(), ex);
+ } else if (ex.javaException) {
+ return new net.appjet.bodylock.JSRuntimeException(ex.javaException.getMessage(), ex.javaException);
+ } else if (ex.rhinoException) {
+ return new net.appjet.bodylock.JSRuntimeException(ex.rhinoException.getMessage(), ex.rhinoException);
+ } else {
+ return ex;
+ }
+}
+
+function _convertStackFrameToTable(id, frame) {
+ var r = frame.errorContext(4);
+ var out = [];
+ var t = TABLE({className: "codecontext"});
+ var counter = r._1();
+ r._3().foreach(scalaF1(function(s) {
+ var row = TR(TD({className: "linecell"}, counter++), TD(String(s)));
+ if (counter-1 == frame.errorLine())
+ row[1].attribs['class'] = "offendingline";
+ t.push(row);
+ }));
+ if (id != 0)
+ out.push(DIV({className: "errorframe",
+ onclick: "toggleFrameView('"+id+"')"},
+ IMG({className: "zippy", style: "margin-right: 0.5em;", align: "top", src: "http://appjet.com/img/open-arrow.png", id: "image"+id}),
+ SPAN({className: "errordetail"}, "...was called from "+frame.name()+ " (line "+frame.errorLine()+"):"),
+ SPAN({className: "sourceline"}, " "+frame.errorContext(0)._3().first())));
+ out.push(DIV({id: 'frame'+id, style: (id == 0 ? "" : "display: none;")}, t));
+ return out.map(function(tag) { return toHTML(tag); }).join("");
+}
+
+function getStackTraceHTML(ex) {
+ ex = _getException(ex);
+ if (ex.frames().isEmpty())
+ return "No stack trace available.";
+ var out = [];
+ var counter = 0;
+ var firstFrame = ex.frames().first();
+ out.push(toHTML(DIV({id: "errortitle"}, "Error in "+firstFrame.name())));
+ out.push(toHTML(DIV({id: "errormessage"}, ""+ex.cause().getMessage()+" at "+firstFrame.name()+" (Line "+firstFrame.errorLine()+")")));
+ ex.frames().foreach(scalaF1(function(frame) {
+ out.push(_convertStackFrameToTable(counter++, frame));
+ }));
+ return out.join("");
+}
+
+function getStackTraceFullpage(ex) {
+ var tmpl = new EJS({text: _tmpl});
+ return tmpl.render({trace: getStackTraceHTML(ex)});
+}
+
+function getStackTracePlain(ex) {
+ ex = _getException(ex);
+ if (ex.frames().isEmpty()) {
+ var cause = ex.cause();
+ var sw = new java.io.StringWriter();
+ cause.printStackTrace(new java.io.PrintWriter(sw));
+ return sw.toString();
+ }
+ var out = [];
+ var firstFrame = ex.frames().first();
+ out.push("Error in "+firstFrame.name());
+ out.push(""+ex.cause().getMessage()+" at "+firstFrame.name()+" (Line "+firstFrame.errorLine()+")");
+ var counter = 0;
+ ex.frames().foreach(scalaF1(function(frame) {
+ if (counter++ > 0) {
+ out.push("");
+ out.push("...was called from "+frame.name()+" (line "+frame.errorLine()+"): "+frame.errorContext(0)._3().first());
+ }
+ var r = frame.errorContext(4);
+ var c2 = r._1();
+ r._3().foreach(scalaF1(function(s) {
+ var pre = " ";
+ if (c2 == frame.errorLine())
+ pre = ">";
+ out.push(sprintf("%s %4s | %s", pre, ""+c2, s));
+ c2++;
+ }));
+ }));
+ return out.join("\n");
+}
+
+/* template follows */
+var _tmpl = """<!DOCTYPE HTML PUBLIC
+ "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+ <title>AppJet Error</title>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en-us" />
+ <script type="text/javascript" src="http://appjet.com/js/343607636acfee88faa2b638330a3370/jquery-1.2.6.js"></script>
+ <script type="text/javascript">
+function toggleFrameView(frameId) {
+ var hidden = $("#frame"+frameId+":hidden")
+ var visible = $("#frame"+frameId+":visible")
+ if (hidden.length > 0) {
+ hidden.show("normal")
+ $("#image"+frameId).attr("src", "http://appjet.com/img/close-arrow.png")
+ } else {
+ visible.hide("normal")
+ $("#image"+frameId).attr("src", "http://appjet.com/img/open-arrow.png")
+ }
+}
+
+function toggleErrorView() {
+ var hidden = $("#hiddentrace:hidden");
+ var visible = $("#hiddentrace:visible");
+ if (hidden.length > 0) {
+ hidden.slideDown("normal");
+ } else {
+ visible.slideUp("normal");
+ }
+ return false;
+}
+
+function load() {
+ $(".zippy").attr("src", "http://appjet.com/img/open-arrow.png")
+}
+</script>
+<style>
+body {
+ font-family: verdana, helvetica, sans-serif;
+ font-size: 60%;
+ margin: 1em;
+}
+#header { border-bottom: 1px solid red; margin-bottom: 0.8em; }
+#errortitle { font-weight: bold; font-size: 1.6em; margin-bottom: 0.76em;}
+#errormessage { font-size: 1.4em; margin-bottom: 1.0em}
+#errorexplanation {
+ background-color: #ffd; margin: 1em; padding: 0 1em;
+ border: 1px solid #cc9;
+ line-height: 150%;
+}
+#errorexplanation ul, #errorexplanation li { margin: 0; padding: 0; }
+#errorexplanation ul { padding-left: 2em; }
+#errorexplanation { font-size: 9pt; }
+#errorexplanation code { font-size: 10pt; }
+#errorexplanation code { padding: 1px; background: #ddc; margin: 0 5px;
+ white-space:nowrap; }
+#errorexplanation code.quote { background: #fcc; }
+#errorexplanation p, #errorexplanation li { margin: 1em 0 }
+#frame0 { margin-top: 2.0em; }
+.errorframe {
+ margin-bottom: 0.5em;
+ margin-top: 1em;
+ cursor: pointer;
+ color: blue;
+}
+.errordetail {
+ text-decoration: underline;
+}
+.errorframe:hover {
+ color: #c47827;
+}
+.sourceline {
+ font-size: 1.4em;
+ color: black;
+ font-family: monospace;
+}
+#statuscode {
+ float: right;
+ font-size: 2.4em;
+ font-weight: bold;
+}
+.codecontext {
+ border: 1px solid black;
+ font-family: monospace;
+ font-size: 1.4em;
+}
+.codecontext td { padding: 0.1em 0.3em; }
+.codecontext .linecell { border-right: 1px solid #888; }
+.codecontext .offendingline { background-color: #ffcccc; }
+.errotemplate .codecontext .linecell { border-right: 1px solid black; }
+pre {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+#errorexplanation tt { font-size: 85%; color: #666; }
+</style>
+</head>
+<body onload="load()">
+<%= trace %>
+</body>
+</html>"""
diff --git a/trunk/infrastructure/framework-src/modules/execution.js b/trunk/infrastructure/framework-src/modules/execution.js
new file mode 100644
index 0000000..1cec418
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/execution.js
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("jsutils.{scalaF0,scalaF1}");
+
+/**
+ * Asynchronously call a function as soon as the current request completes.
+ **/
+function async(f) {
+ Packages.net.appjet.ajstdlib.execution.runAsync(appjet.context, f);
+}
+
+function initTaskThreadPool(name, poolSize) {
+ Packages.net.appjet.ajstdlib.execution.createNamedTaskThreadPool(name, poolSize);
+}
+
+function scheduleTask(poolName, taskName, delayMillis, args) {
+ return Packages.net.appjet.ajstdlib.execution.scheduleTaskInPool(poolName, taskName, delayMillis, args);
+}
+
+function shutdownAndWaitOnTaskThreadPool(poolName, timeoutMillis) {
+ return Packages.net.appjet.ajstdlib.execution.shutdownAndWaitOnTaskThreadPool(poolName, timeoutMillis);
+}
+
+function fancyAssEval(initCode, mainCode) {
+ function init(runner) {
+ Packages.net.appjet.bodylock.BodyLock.evaluateString(
+ runner.globalScope(),
+ initCode,
+ "eval'd code imports",
+ 1);
+ }
+ var runner = Packages.net.appjet.oui.ScopeReuseManager.getEmpty(scalaF1(init));
+ var ec = new Packages.net.appjet.oui.ExecutionContext(
+ new Packages.net.appjet.oui.RequestWrapper(request.underlying),
+ null, runner);
+ return Packages.net.appjet.oui.ExecutionContextUtils.withContext(ec,
+ scalaF0(function() {
+ return Packages.net.appjet.bodylock.BodyLock.evaluateString(
+ runner.globalScope(),
+ mainCode,
+ "eval'd code main",
+ 1);
+ }));
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/modules/fastJSON.js b/trunk/infrastructure/framework-src/modules/fastJSON.js
new file mode 100644
index 0000000..3198b96
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/fastJSON.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+jimport("net.appjet.oui.FastJSON");
+jimport("java.lang.System.out.println");
+
+function stringify(x) {
+ return String(FastJSON.stringify(x));
+}
+
+function parse(x) {
+ return FastJSON.parse(appjet.context, x);
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/faststatic.js b/trunk/infrastructure/framework-src/modules/faststatic.js
new file mode 100644
index 0000000..5cca676
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/faststatic.js
@@ -0,0 +1,318 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview serving static files, including js and css, and cacheing
+ * and minifying.
+ *
+ * Terminology Note:
+ * "path" is confusing because paths can be part of URLs and part
+ * of filesystem paths, and static files have both types of paths
+ * associated with them. Therefore, in this module:
+ *
+ * LOCALDIR or LOCALFILE refers to directories or files on the filesystem.
+ *
+ * HREF is used to describe things that go in a URL.
+ */
+
+import("fileutils.{readFile,readFileBytes}");
+import("yuicompressor");
+import("stringutils");
+import("varz");
+import("ejs.EJS");
+
+jimport("java.lang.System.out.println");
+
+//----------------------------------------------------------------
+// Content Type Guessing
+//----------------------------------------------------------------
+
+var _contentTypes = {
+ 'gif': 'image/gif',
+ 'png': 'image/png',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'css': 'text/css',
+ 'js': 'application/x-javascript',
+ 'txt': 'text/plain',
+ 'html': 'text/html; charset=utf-8',
+ 'ico': 'image/x-icon',
+ 'swf': 'application/x-shockwave-flash',
+ 'zip': 'application/zip',
+ 'xml': 'application/xml'
+};
+
+var _gzipableTypes = {
+ 'text/css': true,
+ 'application/x-javascript': true,
+ 'text/html; charset=utf-8': true
+};
+
+function _guessContentType(path) {
+ var ext = path.split('.').pop().toLowerCase();
+ return _contentTypes[ext] || 'text/plain';
+}
+
+//----------------------------------------------------------------
+
+function _getCache(name) {
+ var m = 'faststatic';
+ if (!appjet.cache[m]) {
+ appjet.cache[m] = {};
+ }
+ var c = appjet.cache[m];
+ if (!c[name]) {
+ c[name] = {};
+ }
+ return c[name];
+}
+
+var _mtimeCheckInterval = 5000; // 5 seconds
+
+function _getMTime(f) {
+ var mcache = _getCache('mtimes');
+ var now = +(new Date);
+ if (appjet.config.devMode ||
+ !(mcache[f] && (now - mcache[f].lastCheck < _mtimeCheckInterval))) {
+ var jfile = new net.appjet.oui.JarVirtualFile(f);
+ if (jfile.exists() && !jfile.isDirectory()) {
+ mcache[f] = {
+ lastCheck: now,
+ mtime: jfile.lastModified()
+ };
+ } else {
+ mcache[f] = null;
+ }
+ }
+ if (mcache[f]) {
+ return +mcache[f].mtime;
+ } else {
+ return null;
+ }
+}
+
+function _wrapFile(localFile) {
+ return {
+ getPath: function() { return localFile; },
+ getMTime: function() { return _getMTime(localFile); },
+ getContents: function() { return _readFileAndProcess(localFile, 'string'); }
+ };
+}
+
+function _readFileAndProcess(fileName, type) {
+ if (fileName.slice(-8) == "_ejs.css") {
+ // run CSS through EJS
+ var template = readFile(fileName);
+ var ejs = new EJS({text:template, name:fileName});
+ var resultString = ejs.render({});
+ if (type == 'bytes') {
+ return new java.lang.String(resultString).getBytes("UTF-8");
+ }
+ else {
+ return resultString;
+ }
+ }
+ else if (type == 'string') {
+ return readFile(fileName);
+ }
+ else if (type == 'bytes') {
+ return readFileBytes(fileName);
+ }
+}
+
+function _cachedFileBytes(f) {
+ var mtime = _getMTime(f);
+ if (!mtime) { return null; }
+ var fcache = _getCache('file-bytes-cache');
+ if (!(fcache[f] && (fcache[f].mtime == mtime))) {
+ varz.incrementInt("faststatic-file-bytes-cache-miss");
+ var bytes = _readFileAndProcess(f, 'bytes');
+ if (bytes) {
+ fcache[f] = {mtime: mtime, bytes: bytes};
+ };
+ }
+ if (fcache[f] && fcache[f].bytes) {
+ return fcache[f].bytes;
+ } else {
+ return null;
+ }
+}
+
+function _shouldGzip(contentType) {
+ var userAgent = request.headers["User-Agent"];
+ if (! userAgent) return false;
+ if (! (/Firefox/.test(userAgent) || /webkit/i.test(userAgent))) return false;
+ if (! _gzipableTypes[contentType]) return false;
+
+ return request.acceptsGzip;
+}
+
+function _getCachedGzip(original, key) {
+ var c = _getCache("gzipped");
+ if (! c[key] || ! java.util.Arrays.equals(c[key].original, original)) {
+ c[key] = {original: original,
+ gzip: stringutils.gzip(original)};
+ }
+ return c[key].gzip;
+}
+
+function _setGzipHeader() {
+ response.setHeader("Content-Encoding", "gzip");
+}
+
+//----------------------------------------------------------------
+
+/**
+ * Function for serving a single static file.
+ */
+function singleFileServer(localPath, opts) {
+ var contentType = _guessContentType(localPath);
+
+ return function() {
+ (opts.cache ? response.alwaysCache() : response.neverCache());
+ response.setContentType(contentType);
+ var bytes = _cachedFileBytes(localPath);
+ if (bytes) {
+ if (_shouldGzip(contentType)) {
+ bytes = _getCachedGzip(bytes, "file:"+localPath);
+ _setGzipHeader();
+ }
+ response.writeBytes(bytes);
+ return true;
+ } else {
+ return false;
+ }
+ };
+}
+
+/**
+ * valid opts:
+ * alwaysCache: default false
+ */
+function directoryServer(localDir, opts) {
+ if (stringutils.endsWith(localDir, "/")) {
+ localDir = localDir.substr(0, localDir.length-1);
+ }
+ return function(relpath) {
+ if (stringutils.startsWith(relpath, "/")) {
+ relpath = relpath.substr(1);
+ }
+ if (relpath.indexOf('..') != -1) {
+ response.forbid();
+ }
+ (opts.cache ? response.alwaysCache() : response.neverCache());
+ var contentType = _guessContentType(relpath);
+ response.setContentType(contentType);
+ var fullPath = localDir + "/" + relpath;
+ var bytes = _cachedFileBytes(fullPath);
+
+ if (bytes) {
+ if (_shouldGzip(contentType)) {
+ bytes = _getCachedGzip(bytes, "file:"+fullPath);
+ _setGzipHeader();
+ }
+ response.writeBytes(bytes);
+ return true;
+ } else {
+ return false;
+ }
+ };
+}
+
+/**
+ * Serves cat files, which are concatenated versions of many files.
+ */
+function compressedFileServer(opts) {
+ var cfcache = _getCache('compressed-files');
+ return function() {
+ var key = request.path.split('/').slice(-1)[0];
+ var contentType = _guessContentType(request.path);
+ response.setContentType(contentType);
+ response.alwaysCache();
+ var data = cfcache[key];
+ if (data) {
+ if (_shouldGzip(contentType)) {
+ data = _getCachedGzip((new java.lang.String(data)).getBytes(response.getCharacterEncoding()), "comp:"+key);
+ _setGzipHeader();
+ response.writeBytes(data);
+ } else {
+ response.write(data);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ };
+}
+
+function getCompressedFilesKey(type, baseLocalDir, localFileList) {
+ if (stringutils.endsWith(baseLocalDir, '/')) {
+ baseLocalDir = baseLocalDir.substr(0, baseLocalDir.length-1);
+ }
+
+ var fileList = [];
+ // convert passed-in file list into list of our file objects
+ localFileList.forEach(function(f) {
+ if (typeof(f) == 'string') {
+ fileList.push(_wrapFile(baseLocalDir+'/'+f));
+ } else {
+ fileList.push(f);
+ }
+ });
+
+ // have we seen this exact fileset before?
+ var fsId = fileList.map(function(f) { return f.getPath(); }).join('|');
+ var fsMTime = Math.max.apply(this,
+ fileList.map(function(f) { return f.getMTime(); }));
+
+ var kdcache = _getCache('fileset-keydata-cache');
+ if (!(kdcache[fsId] && (kdcache[fsId].mtime == fsMTime))) {
+ //println("cache miss for fileset: "+fsId);
+ //println("compressing fileset...");
+ kdcache[fsId] = {
+ mtime: fsMTime,
+ keyString: _compressFilesAndMakeKey(type, fileList)
+ };
+ }
+ return kdcache[fsId].keyString;
+}
+
+function _compressFilesAndMakeKey(type, fileList) {
+ function _compress(s) {
+ if (type == 'css') {
+ varz.incrementInt("faststatic-yuicompressor-compressCSS");
+ return yuicompressor.compressCSS(s);
+ } else if (type == 'js') {
+ varz.incrementInt("faststatic-yuicompressor-compressJS");
+ return yuicompressor.compressJS(s);
+ } else {
+ throw Error('Dont know how to compress this filetype: '+type);
+ }
+ }
+
+ var fullstr = "";
+ fileList.forEach(function(f) {
+ fullstr += _compress(f.getContents());
+ });
+
+ fullstr = _compress(fullstr);
+
+ var key = stringutils.md5(fullstr) + '.' + type;
+ var cfcache = _getCache('compressed-files');
+ cfcache[key] = fullstr;
+ return key;
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/fileutils.js b/trunk/infrastructure/framework-src/modules/fileutils.js
new file mode 100644
index 0000000..aaf12e2
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/fileutils.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @fileOverview misc file functions */
+
+jimport("java.io.File",
+ "java.io.DataInputStream",
+ "java.io.FileInputStream",
+ "java.lang.reflect.Array",
+ "java.lang.Byte",
+ "java.io.FileReader",
+ "java.io.BufferedReader",
+ "net.appjet.oui.JarVirtualFile");
+
+function readFileBytes(path) {
+ var jfile = new JarVirtualFile(path);
+ if (!jfile.exists() || jfile.isDirectory()) {
+ throw 'Not a file: '+path;
+ }
+ return net.appjet.common.util.BetterFile.getStreamBytes(jfile.openStream());
+}
+
+function readFile(path) {
+ var bytes = readFileBytes(path);
+ if (bytes !== null) {
+ return String(new java.lang.String(bytes));
+ } else {
+ return null;
+ }
+}
+
+function fileLastModified(path) {
+ var jfile = new JarVirtualFile(path);
+ if (!jfile.exists()) {
+ throw "Not a file: "+path;
+ }
+ return jfile.lastModified();
+}
+
+//----------------------------------------------------------------
+// real files
+//----------------------------------------------------------------
+
+function readRealFileBytes(path) {
+ var jfile = new File(path);
+ if (!jfile.exists() || jfile.isDirectory()) {
+ throw 'Not a real file: '+path;
+ }
+ var jdata = new DataInputStream(new FileInputStream(jfile));
+ var size = jfile.length();
+ var bytes = Array.newInstance(Byte.TYPE, size);
+ jdata.read(bytes, 0, size);
+ jdata.close();
+ return bytes;
+}
+
+function readRealFile(path) {
+ var bytes = readRealFileBytes(path);
+ if (bytes !== null) {
+ return String(new java.lang.String(bytes));
+ } else {
+ return null;
+ }
+}
+
+function writeRealFile(path, data) {
+ var jf = new Packages.java.io.File(path);
+ var fw = new Packages.java.io.FileWriter(jf);
+ fw.write(data);
+ fw.flush();
+ fw.close();
+}
+
+
+function eachFileLine(file, fn) {
+ var iter = fileLineIterator(file);
+ while (iter.hasNext) {
+ fn(iter.next);
+ }
+}
+
+function fileLineIterator(file) {
+ var reader = new BufferedReader(new FileReader(file));
+ var nextLine = reader.readLine();
+ return {
+ get hasNext() { return nextLine !== null },
+ get next() {
+ var curLine = nextLine;
+ if (this.hasNext) {
+ nextLine = reader.readLine();
+ }
+ return curLine;
+ }
+ };
+}
diff --git a/trunk/infrastructure/framework-src/modules/funhtml.js b/trunk/infrastructure/framework-src/modules/funhtml.js
new file mode 100644
index 0000000..c27b667
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/funhtml.js
@@ -0,0 +1,158 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview Functional HTML tag writing.<br/>
+ *
+ * <p>This library allows you to write HTML in the form of nested function
+ * calls. By default, a function is predefined for each tag, with the function
+ * name being in all caps. A dictionary of HTML attributes can optionally be
+ * passed as a first argument to a tag; other arguments become child tags.
+ * Attribute names that conflict with JavaScript
+ * keywords have been renamed; use "className" in place of "class" and
+ * "htmlFor" in place of "for".</p>
+ *
+ * <p>Tag objects inherit from Array, so array methods can be used to
+ * manipulate a tag's list of child tags.</p>
+ *
+ * @example
+print(P({id:"sec3"},"Tags are ",B(I("crazy"))," awesome."));
+ */
+
+import("jsutils.eachProperty");
+import("stringutils");
+import("stringutils.toHTML");
+
+function html(x) {
+ // call out to stringutils.html().
+ var args = Array.prototype.slice.call(arguments);
+ return stringutils.html.apply(this, args);
+};
+
+function toHTML(x) {
+ // call out to stringutils.toHTML().
+ var args = Array.prototype.slice.call(arguments);
+ return stringutils.toHTML.apply(this, args)
+};
+
+//----------------------------------------------------------------
+// tags.
+//----------------------------------------------------------------
+
+var _neverSingletones = {
+ 'TEXTAREA': true,
+ 'SCRIPT': true,
+ 'DIV': true,
+ 'IFRAME': true,
+ 'UL': true,
+ 'TABLE': true
+};
+
+/**
+ * Imports a specified list of tags. All HTML tags are automatically imported
+ * by default, but you may wish to use the tag library to write other kinds
+ * of mark-up. For each tag you want to import, pass in the name (including
+ * any punctuation) with the (upper/lower) case you want to use for the function
+ * (traditionally all uppercase). The function name will have punctuation
+ * replaced with underscores, and the printed tag will be all lowercase.
+ *
+ * @param {object} scopeObj where to define the tags; to define in the global scope, pass <code>this</code> from the top level (not from inside a function)
+ * @param {array} tagArray an array of strings, the tags to import
+ * @example
+importTags(this, ["MEDIA:TITLE"]);
+print(MEDIA_TITLE({type:"html"}, "funny pictures"));
+// prints &lt;media:title type="html"&gt;funny pictures&lt;/media:title&gt;
+ */
+function _importTags(scopeObj, tagArray) {
+ tagArray.forEach(function(arg) {
+ var funcName = arg.replace(/:/g, "_").replace(/-/g, "_");
+ var tagName = arg.toLowerCase();
+ scopeObj[funcName] = function() {
+ var tag = [];
+ tag.name = tagName;
+ var contents = Array.prototype.slice.call(arguments);
+ if (contents.length > 0) {
+ if (contents[0] &&
+ (! contents[0].toHTML) &&
+ ((typeof contents[0]) == "object") &&
+ (! Array.prototype.isPrototypeOf(contents[0])) &&
+ (! Date.prototype.isPrototypeOf(contents[0]))) {
+ // first arg is attributes
+ tag.attribs = contents[0];
+ contents.shift();
+ }
+ else {
+ tag.attribs = {};
+ }
+ contents.forEach(function (content) {
+ tag.push(content);
+ });
+ }
+ else {
+ tag.attribs = {};
+ }
+ tag.toString = function() { return this.toHTML(); }; // this behavior is relied on
+ tag.toHTML = function() {
+ var t = this;
+ var result = [];
+ result.add = function(x) { this.push(x); return this; };
+ result.add('<').add(t.name);
+ if (t.attribs) {
+ eachProperty(t.attribs, function(k,v) {
+ if (k == "className") k = "class";
+ if (k == "htmlFor") k = "for";
+ if (!(v === undefined)) {
+ // escape quotes and newlines in values
+ v = String(v).replace(/\"/g, '\\"').replace(/\n/g, '\\n');
+ result.add(' ').add(k).add('="').add(v).add('"');
+ }
+ });
+ }
+ if ((t.length < 1) && (!(t.name.toUpperCase() in _neverSingletones))) {
+ result.add(' />');
+ }
+ else {
+ result.add('>');
+ t.forEach(function (x) {
+ result.add(toHTML(x));
+ });
+ result.add('</').add(t.name).add('\n>');
+ }
+ return result.join("");
+ };
+ return tag;
+ };
+ });
+}
+
+var _html_tags =
+ ["A", "ABBR", "ACRONYM", "ADDRESS", "APPLET", "AREA", "B",
+ "BASE", "BASEFONT", "BDO", "BIG", "BLOCKQUOTE", "BODY",
+ "BR", "BUTTON", "CAPTION", "CENTER", "CITE", "CODE", "COL",
+ "COLGROUP", "DD", "DEL", "DIR", "DIV", "DFN", "DL", "DT",
+ "EM", "FIELDSET", "FONT", "FORM", "FRAME", "FRAMESET",
+ "H1", "H2", "H3", "H4", "H5", "H6",
+ "HEAD", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT",
+ "INS", "ISINDEX", "KBD", "LABEL", "LEGEND", "LI", "LINK",
+ "MAP", "MENU", "META", "NOFRAMES", "NOSCRIPT", "OBJECT",
+ "OL", "OPTGROUP", "OPTION", "P", "PARAM", "PRE", "Q", "S",
+ "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRIKE",
+ "STRONG", "STYLE", "SUB", "SUP", "TABLE", "TBODY", "TD",
+ "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR", "TT",
+ "U", "UL", "VAR", "XMP"];
+
+_importTags(this, _html_tags);
+
diff --git a/trunk/infrastructure/framework-src/modules/global/appjet.js b/trunk/infrastructure/framework-src/modules/global/appjet.js
new file mode 100644
index 0000000..135ac44
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/global/appjet.js
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("jsutils.scalaF0");
+
+//----------------------------------------------------------------
+// global static "appjet" object
+//----------------------------------------------------------------
+
+/**
+ * @fileOverview The global appjet object contains access to the AppJet runtime,
+ * app meta-data, and other information.
+ */
+var appjet = {
+
+/**
+ * This is the interface to the execution context. You probably won't need
+ * to use this, but if you do, be careful!
+ * @type object
+ */
+get context() {
+ return net.appjet.oui.ExecutionContextUtils.currentContext();
+},
+
+get executionId() {
+ return this.context.executionId();
+},
+
+// /**
+// * Holds the current request's requestId. (These IDs may be reused!)
+// * @type String
+// */
+// get requestId() {
+// return this.context.requestId();
+// },
+
+/**
+ * Volatile cache that persists between requests. (JavaScript object).
+ */
+get cache() {
+ return Packages.net.appjet.ajstdlib.ajstdlib.attributes()
+ .getOrElseUpdate("cache", scalaF0({}));
+},
+
+get cacheRoot() {
+ return function(name) {
+ return Packages.net.appjet.ajstdlib.ajstdlib.attributes()
+ .getOrElseUpdate("cache-"+(name?name:""), scalaF0({}));
+ };
+},
+
+/**
+ * A global lock for this app (ReentrantLock object).
+ */
+get globalLock() {
+ return net.appjet.ajstdlib.ajstdlib.globalLock();
+},
+
+/**
+ * Per-request cache, cleared between requests.
+ */
+get requestCache() {
+ return this.context.attributes().getOrElseUpdate("requestCache", scalaF0({}))
+},
+
+/**
+ * Per-scope cache, persisted in this "server" instance.
+ */
+get scopeCache() {
+ return this.context.runner().attributes().getOrElseUpdate("scopeCache", scalaF0({}));
+},
+
+/**
+ * config params for app.
+ */
+get config() {
+ return Packages.net.appjet.oui.config.configObject(this.context.runner().globalScope());
+},
+
+/**
+ * tells appjet not to re-use this "scope"/"server"
+ */
+get retireScope() {
+ return function() { this.context.runner().reuseOk_$eq(false); }
+},
+
+/**
+ * How many milliseconds the server has been running for.
+ */
+get uptime() {
+ return Date.now() - Packages.net.appjet.oui.main.startTime().getTime();
+}
+
+}; // end: var appjet = {... \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/modules/global/request.js b/trunk/infrastructure/framework-src/modules/global/request.js
new file mode 100644
index 0000000..a4327f9
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/global/request.js
@@ -0,0 +1,312 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("stringutils.trim");
+import("jsutils.scalaF0")
+
+function _cx() { return appjet.context };
+
+function _addIfNotPresent(obj, key, value) {
+ if (!(key in obj)) obj[key] = value;
+}
+
+var request = {
+
+get isDefined() {
+ return (
+ _cx() != null &&
+ _cx().request() != null &&
+ (! _cx().request().isFake()) &&
+ _cx().request().req() != null
+ );
+},
+
+get cache() {
+ var req = _cx().request().req();
+ if (req.getAttribute("jsCache") == null) {
+ req.setAttribute("jsCache", {});
+ }
+ return req.getAttribute("jsCache");
+},
+
+get continuation() {
+ if (this.isDefined) {
+ var c = Packages.net.appjet.ajstdlib.execution.getContinuation(_cx());
+ var u = this.underlying;
+ return {
+ suspend: function(timeout) {
+ return Packages.net.appjet.ajstdlib.execution.sync(
+ u, scalaF0(function() { return c.suspend(timeout); }));
+ },
+ resume: function() {
+ Packages.net.appjet.ajstdlib.execution.sync(
+ u, scalaF0(function() { c.resume(); }))
+ }
+ }
+ }
+},
+
+get underlying() {
+ if (this.isDefined) {
+ return _cx().request().req();
+ }
+},
+
+/**
+ * The request path following the hostname. For example, if the user
+ * is visiting yourapp.appjet.net/foo, then this will be set to
+ * "/foo".
+ *
+ * This does not include CGI parameters or the domain name, and always
+ * begins with a "/".
+ *
+ * @type string
+ */
+get path() {
+ if (this.isDefined) {
+ return String(_cx().request().path());
+ }
+},
+
+/**
+ * The value request query string.
+ *
+ * For example, if the user visits "yourapp.appjet.net/foo?id=20", then
+ * query will be "id=20".
+ *
+ * @type string
+ */
+get query() {
+ if (this.isDefined) {
+ if (_cx().request().query() != null) {
+ return _cx().request().query();
+ }
+ }
+},
+
+/**
+ * The content of a POST request. Retrieving this value may interfere
+ * with the ability to get post request parameters sent in the body of
+ * a request via the "params" property. Use with care.
+ *
+ * @type string
+ */
+get content() {
+ if (this.isDefined) {
+ if (_cx().request().content() != null) {
+ return _cx().request().content();
+ }
+ }
+},
+
+/**
+ * Either "GET" or "POST" (uppercase).
+ * @type string
+ */
+get method() {
+ if (this.isDefined) {
+ return String(_cx().request().method().toUpperCase());
+ }
+},
+
+/**
+ * Whether the curent HTTP request is a GET request.
+ * @type boolean
+ */
+get isGet() {
+ return (this.method == "GET");
+},
+
+/**
+ * Whether the current HTTP request is a POST request.
+ * @type boolean
+ */
+get isPost() {
+ return (this.method == "POST");
+},
+
+/**
+ * Either "http" or "https" (lowercase).
+ * @type string
+ */
+get scheme() {
+ if (this.isDefined) {
+ return String(_cx().request().scheme());
+ }
+},
+
+/**
+ * Whether the current request arrived using HTTPS.
+ * @type boolean
+ */
+get isSSL() {
+ return (this.scheme == "https");
+},
+
+/**
+ * Holds the IP address of the user making the request.
+ * @type string
+ */
+get clientAddr() {
+ if (this.isDefined) {
+ return String(_cx().request().clientAddr());
+ }
+},
+
+/**
+ * Parameters associated with the request, either from the query string
+ * or from the contents of a POST, e.g. from a form. Parameters are accessible
+ * by name as properties of this object. The property value is either a
+ * string (typically) or an array of strings (if the parameter occurs
+ * multiple times in the request).
+ *
+ * @type object
+ */
+get params() {
+ if (this.isDefined) {
+ var cx = _cx();
+ var req = cx.request();
+ return cx.attributes().getOrElseUpdate("requestParams",
+ scalaF0(function() { return req.params(cx.runner().globalScope()); }));
+ }
+},
+
+/**
+ * Uploaded files associated with the request, from the contents of a POST.
+ *
+ * @type object
+ */
+get files() {
+ if (this.isDefined) {
+ var cx = _cx();
+ var req = cx.request();
+ return cx.attributes().getOrElseUpdate("requestFiles",
+ scalaF0(function() { return req.files(cx.runner().globalScope()); }));
+ }
+},
+
+/**
+ * Used to access the HTTP headers of the current request. Properties are
+ * header names, and each value is either a string (typically) or an
+ * array of strings (if the header occurs multiple times in the request).
+ *
+ * @example
+print(request.headers["User-Agent"]);
+ *
+ * @type object
+ */
+get headers() {
+ if (this.isDefined) {
+ var cx = _cx();
+ var req = cx.request();
+ return cx.attributes().getOrElseUpdate("requestHeaders",
+ scalaF0(function() { return req.headers(cx.runner().globalScope()); }));
+ }
+},
+
+// TODO: this is super inefficient to do each time someone accesses
+// request.cookies.foo. We should probably store _cookies in the requestCache.
+get cookies() {
+ var _cookies = {};
+ var cookieHeaderArray = this.headers['Cookie'];
+ if (!cookieHeaderArray) { return {}; }
+ if (!(cookieHeaderArray instanceof Array))
+ cookieHeaderArray = [cookieHeaderArray];
+ var name, val;
+
+ cookieHeaderArray.forEach(function (cookieHeader) {
+ cookieHeader.split(';').forEach(function(cs) {
+ var parts = cs.split('=');
+ if (parts.length == 2) {
+ name = trim(parts[0]);
+ val = trim(unescape(parts[1]));
+ _addIfNotPresent(_cookies, name, val);
+ }
+ });
+ });
+
+ return _cookies;
+},
+
+/**
+ * Holds the full URL of the request.
+ */
+get url() {
+ if (this.isDefined) {
+ return this.scheme+"://"+this.host+this.path+(this.query ? "?"+this.query : "");
+ }
+},
+
+get host() {
+ if (this.isDefined) {
+ // required by HTTP/1.1 to be present.
+ return String(this.headers['Host']).toLowerCase();
+ }
+},
+
+get domain() {
+ if (this.isDefined) {
+ // like host, but without the port if there is one.
+ return this.host.split(':')[0];
+ }
+},
+
+get uniqueId() {
+ return String(_cx().executionId());
+},
+
+get protocol() {
+ if (this.isDefined) {
+ return String(_cx().request().protocol());
+ }
+},
+
+get userAgent() {
+ if (this.isDefined) {
+ var agentString = (request.headers['User-Agent'] || "?");
+ return {
+ toString: function() { return agentString; },
+ isIPhone: function() { return (agentString.indexOf("(iPhone;") > 0); }
+ };
+ }
+},
+
+get acceptsGzip() {
+ if (this.isDefined) {
+ var headerArray = this.headers["Accept-Encoding"];
+ if (! (headerArray instanceof Array)) {
+ headerArray = [headerArray];
+ }
+ // Want to see if some accept-encoding header OK's gzip.
+ // Starting with: "Accept-Encoding: gzip; q=0.5, deflate; q=1.0"
+ // 1. Split into ["gzip; q=0.5", "delfate; q=1.0"]
+ // 2. See if some entry is gzip with q > 0. (q is optional.)
+ return headerArray.some(function(header) {
+ if (! header) return false;
+ return header.split(/,\s*/).some(function(validEncoding) {
+ if (!validEncoding.indexOf("gzip") == 0) {
+ return false;
+ }
+ if (/q=[0\.]*$/.test(validEncoding)) {
+ return false;
+ }
+ return true;
+ });
+ });
+ }
+}
+
+}; // end: var request = {...
diff --git a/trunk/infrastructure/framework-src/modules/global/response.js b/trunk/infrastructure/framework-src/modules/global/response.js
new file mode 100644
index 0000000..7236920
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/global/response.js
@@ -0,0 +1,294 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview Helpers for the HTTP response.
+ */
+
+/** @ignore */
+function _cx() { return appjet.context };
+
+/** @ignore */
+function _cookiestring(c) {
+ var x = '';
+ if (!c.name) { throw new Error('cookie name is required'); }
+ if (!c.value) { c.value = ''; }
+ x += (c.name + '=' + escape(c.value));
+
+ // expires
+ if (c.expires instanceof Date) {
+ x += ('; expires='+_cookiedate(c.expires));
+ }
+ if (typeof(c.expires) == 'number') {
+ var today = (new Date()).valueOf();
+ var d = new Date(today + 86400000*c.expires);
+ x += ('; expires='+_cookiedate(d));
+ }
+
+ // domain
+ if (c.domain) { x += ('; domain='+c.domain); }
+
+ // path
+ if (c.path) { x += ('; path='+c.path); }
+
+ // secure
+ if (c.secure == true) { x += '; secure'; }
+
+ return x;
+};
+
+/** @ignore */
+function _cookiedate(d) {
+ var x = d.toGMTString();
+ var p = x.split(' ');
+ return [p[0], [p[1], p[2], p[3]].join('-'), p[4], p[5]].join(' ');
+};
+
+var response = {
+
+get isDefined() {
+ return _cx().response() != null;
+}
+
+};
+
+/**
+ * Halts the program immediately and returns 403 Forbidden error to the user.
+ */
+response.forbid = function() {
+ _cx().response().error(403, "Forbidden");
+};
+
+/**
+ * Halts the program immediately.
+ *
+ * @param {boolean} renderCurrentPage if false, an empty page will be rendered,
+ * otherwise calls to print() so far will be displayed. Either way, no more
+ * code will be executed.
+ */
+response.stop = function(renderCurrentPage) {
+ _cx().response().stop();
+};
+
+/**
+ * Halts the program immediately and returns a 404 not found error to the user.
+ */
+response.notFound = function() {
+ _cx().response().error(404, "404: Not found");
+};
+
+/**
+ * Halts the program immediately and sends an HTTP redirect response (302),
+ * redirecting to the given path (relative or absolute).
+ *
+ * @param {string} path The new path
+ */
+response.redirect = function(path) {
+ if ((! path) && path != "") {
+ throw new Error("Invalid redirect: "+path);
+ }
+ if (path.indexOf('/') == 0) {
+ // make sure absolute URL has proper host/port
+ path = request.scheme+"://"+request.host+path;
+ }
+ _cx().response().redirect(path);
+};
+
+/**
+ * Sets the status code in the HTTP response.
+ *
+ * @param {number} newCode
+ */
+response.setStatusCode = function(newCode) {
+ _cx().response().setStatusCode(newCode);
+};
+response.getStatusCode = function() {
+ return _cx().response().getStatusCode();
+};
+
+response.sendError = function(errorCode, errorHtml) {
+ _cx().response().error(errorCode, errorHtml);
+};
+
+response.reset = function() {
+ _cx().response().reset();
+};
+
+/**
+ * Sets any header of the HTTP response.
+ *
+ * @example
+response.setHeader('Cache-Control', 'no-cache');
+ *
+ * @param {string} name
+ * @param {string} value
+ */
+response.setHeader = function(name, value) {
+ _cx().response().setHeader(name, value);
+};
+
+/**
+ * Adds the name,value pair to the headers. Useful for headers that are
+ * allowed to repeat, such as Set-Cookie.
+ *
+ * @param {string} name
+ * @param {string} value
+ */
+response.addHeader = function(name, value) {
+ _cx().response().addHeader(name, value);
+};
+
+/**
+ * Returns the value of a previously-set header. Useful in the
+ * postRequestHandler to see values of headers set during normal
+ * request processing.
+ *
+ * @param {string} name
+ * @return {array} An array of header values. Empty array if none set.
+ */
+response.getHeader = function(name) {
+ if (! this.isDefined) {
+ return [];
+ } else {
+ return _cx().response().getHeader(name);
+ }
+};
+
+/**
+ * Removes all instances of a header of the HTTP response.
+ *
+ * @param {string} name
+ */
+response.removeHeader = function(name) {
+ _cx().response().removeHeader(name);
+};
+
+/**
+ * Low-level hook for writing raw data to the response.
+ * @param {string} data will be written, verbatim, to the HTTP resonse.
+ */
+response.write = function(data) {
+ _cx().response().write(data);
+};
+
+/**
+ * Low-level hook for writing raw byte data to the response. Especially
+ * useful for writing the result of a <code>wget</code> of image data,
+ * or writing an uploaded file.
+ * @param {string} data will be written, verbatim, to the HTTP resonse.
+ */
+response.writeBytes = function(data) {
+ _cx().response().writeBytes(data);
+};
+
+//----------------------------------------------------------------
+// Cookies!
+//----------------------------------------------------------------
+
+/**
+ * Set a cookie in the response.
+ *
+ * @example
+response.setCookie({
+ name: "SessionID",
+ value: "25",
+ secure: true,
+ expires: 14 // 14 days
+});
+ *
+ * @param {object} cookieObject This may contain any of the following:
+<ul>
+ <li>name (required): The name of the cookie</li>
+ <li>value (required): The value of the cookie. (Note: this value will be escaped).
+ <li>expires (optional): If an integer, means number of days until it expires;
+ if a Date object, means exact date on which to expire.</li>
+ <li>domain (optional): The cookie domain</li>
+ <li>path (optional): To restrict the cookie to a specific path.</li>
+ <li>secure (optional): Whether this cookie should only be sent securely.</li>
+</ul>
+ */
+response.setCookie = function(cookieObject) {
+ this.addHeader('Set-Cookie', _cookiestring(cookieObject));
+
+ var p3pHeader = this.getHeader("P3P");
+ if ((! p3pHeader) || p3pHeader.length == 0) {
+ // The existence of this "privacy policy" header allows cookies set on
+ // pages inside iframes to be accepted by IE. (This is some kind of
+ // default policy copied from an example online. -- dgreensp)
+ this.setHeader('P3P', 'CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"');
+ }
+};
+
+/**
+ * Tells the client to delete the cookie of the given name (by setting
+ * its expiration time to zero).
+ * @param {string} name The name of the cookie to delete.
+ */
+response.deleteCookie = function(name) {
+ this.setCookie({name: name, value: '', expires: 0});
+};
+
+function _trim(s) {
+ return String((new java.lang.String(s)).trim());
+}
+
+response.getCookie = function(name) {
+ var cookieHeaders = this.getHeader('Set-Cookie');
+ if (! cookieHeaders) { return; }
+ for (var i = 0; i < cookieHeaders.length; ++i) {
+ if (_trim(cookieHeaders[i].split("=")[0]) == name)
+ return _trim(cookieHeaders[i].split(";")[0].split("=")[1]);
+ }
+};
+
+/**
+ * Sets the Content-Type header of the response. If the content-type includes
+ * a charset, that charset is used to send the response.
+ * @param {string} contentType the new content-type
+ */
+response.setContentType = function(contentType) {
+ _cx().response().setContentType(contentType);
+};
+
+response.getCharacterEncoding = function() {
+ return _cx().response().getCharacterEncoding();
+}
+
+response.neverCache = function() {
+ // be aggressive about not letting the response be cached.
+ var that = this;
+ function sh(k,v) { that.setHeader(k,v); }
+ sh('Expires', 'Sat, 18 Jun 1983 07:07:07 GMT');
+ sh('Last-Modified', (new Date()).toGMTString());
+ sh('Cache-Control', ('no-store, no-cache, must-revalidate, '+
+ 'post-check=0, pre-check=0'));
+ sh('Pragma', 'no-cache');
+};
+
+response.alwaysCache = function() {
+ var that = this;
+ function sh(k,v) { that.setHeader(k,v); }
+ that.removeHeader('Last-Modified');
+ that.removeHeader('Pragma');
+ var futureDate = new Date();
+ futureDate.setTime(Date.now() + 315360000000);
+ sh('Expires', futureDate.toGMTString());
+ sh('Cache-Control', 'max-age=315360000');
+};
+
+response.setGzip = function(gzip) {
+ _cx().response().setGzip(gzip);
+}
diff --git a/trunk/infrastructure/framework-src/modules/image.js b/trunk/infrastructure/framework-src/modules/image.js
new file mode 100644
index 0000000..8aec74b
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/image.js
@@ -0,0 +1,110 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("blob");
+
+jimport("java.awt.image.BufferedImage");
+jimport("org.apache.sanselan.Sanselan");
+jimport("org.apache.sanselan.ImageFormat");
+
+if (java.lang.System.getProperty("java.awt.headless") == null) {
+ // If the system property isn't set either way, then default to "headless" mode,
+ // so that we don't start calling the window manager when the AWT classes
+ // are loaded. For example, on OS X the java process is given a Dock icon
+ // when we create our first BufferedImage.
+ java.lang.System.setProperty("java.awt.headless", "true");
+}
+
+/**
+ * Encodes the given pixel data into an image blob, ready to be served to
+ * the client. Pixels are specified as 32-bit ints in the format AARRGGBB,
+ * and the order is across rows first, then down columns. If useTransparency
+ * is true, then all pixels should have an alpha channel as their
+ * most-significant byte, with 0xff being fully opaque. If useTransparency
+ * is false, all pixels are fully opaque and the high byte is ignored.
+ * Supported formats: GIF.
+ * <p>
+ * For example, to create a GIF image consisting of a green pixel followed
+ * by a transparent pixel to the right of it, use:
+ * imageBlobFromPixels(2, 1, [0xff00ff00, 0x00000000], true, "gif")
+ */
+
+function pixelsToImageBlob(width, height, pixelArrayARGB, useTransparency, format) {
+ var image = _makeBufferedImage(width, height);
+ var array = _makePixelArray(width, height);
+ var alphaMask = (useTransparency ? 0x00000000 : 0xff000000);
+
+ for(var i=0; i<array.length; i++) {
+ // bitwise operations cause a JS number to become a signed 32-bit int
+ array[i] = (pixelArrayARGB[i] | alphaMask);
+ }
+
+ _setImagePixels(image, array);
+
+ if (format.toLowerCase() == "gif") {
+ return _bufferedImage2gifBlob(image);
+ }
+ return null;
+}
+
+/**
+ * Creates a blob of image data in a format readable by a web browser
+ * that consists of a solid, opaque color and has the given width
+ * and height. The sixHexDigits must be a number, such as
+ * 0x12fda3, or a string, such as "12fda3".
+ */
+function solidColorImageBlob(width, height, sixHexDigits) {
+ var image = _makeBufferedImage(width, height);
+ var array = _makePixelArray(width, height);
+
+ var pixel = 0xffffff;
+ if ((typeof sixHexDigits) == "number") {
+ pixel = sixHexDigits;
+ }
+ else if ((typeof sixHexDigits) == "string") {
+ pixel = Number("0x"+sixHexDigits);
+ }
+
+ // bitwise operations cause a JS number to become a signed 32-bit int
+ pixel = ((pixel & 0xffffff) | 0xff000000);
+
+ java.util.Arrays.fill(array, pixel);
+ _setImagePixels(image, array);
+
+ return _bufferedImage2gifBlob(image);
+}
+
+function _makeBufferedImage(width, height) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+}
+
+function _makePixelArray(width, height) {
+ return java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE,
+ width*height);
+}
+
+function _setImagePixels(image, array) {
+ var width = image.getWidth();
+ var height = image.getHeight();
+
+ image.setRGB(0, 0, width, height, array, 0, width);
+}
+
+function _bufferedImage2gifBlob(image) {
+ // Use the Apache Sanselan image library because it nails transparent GIFs.
+ var bytes = Sanselan.writeImageToBytes(image, ImageFormat.IMAGE_FORMAT_GIF, null);
+ return blob.byteArrayToBlob("image/gif", bytes);
+}
diff --git a/trunk/infrastructure/framework-src/modules/jsutils.js b/trunk/infrastructure/framework-src/modules/jsutils.js
new file mode 100644
index 0000000..02f81a2
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/jsutils.js
@@ -0,0 +1,195 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview A collection of core JavaScript utilities.
+ */
+
+/**
+ * Iterator convenience for JavaScript Objects.
+ *
+ * Note that if func returns false, the iteration will be immediately terminated.
+ * (Returning undefined, or not specifying a return type, does not terminate the iteration).
+ *
+ * @example
+var pastels = {
+ red: "#fcc",
+ green: "#cfc",
+ blue: "#ccf"
+};
+eachProperty(pastels, function(key, value) {
+ print(DIV({style: 'background: '+value+';'}, key));
+});
+ *
+ * @param {object} obj The object over which to iterate.
+ * @param {function} func The function to run on each [key,value] pair.
+ */
+function eachProperty(obj, func) {
+ var r;
+ for (k in obj) {
+ if (!obj.hasOwnProperty || obj.hasOwnProperty(k)) {
+ r = func(k,obj[k]);
+ if (r === false) {
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Douglas Crockford's "object" function for prototypal inheritance, taken from
+ * http://javascript.crockford.com/prototypal.html
+ *
+ * @param {object} parent The parent object.
+ * @return {object} A new object whose prototype is parent.
+ */
+function object(parent) {
+ function f() {};
+ f.prototype = parent;
+ return new f();
+}
+
+/**
+ * Creates an array of the properties of <code>obj</code>,
+ * <em>not</em> including built-in or inherited properties. If no
+ * argument is given, applies to the global object.
+ *
+ * @example
+// Prints "abc"
+keys({a: 1, b: 2, c: 3}).forEach(function(k) {
+ print(k);
+}
+ *
+ * @example
+// Prints all the functions and object members of the global "appjet" object,
+// one per line.
+print(keys(appjet).join('\n'));
+ *
+ * @param {object} obj
+ */
+function keys(obj) {
+ var array = [];
+ var o = obj;
+ if (o == undefined) {
+ o = this;
+ }
+ for(var k in o) {
+ if (!obj.hasOwnProperty || o.hasOwnProperty(k)) {
+ array.push(k);
+ }
+ }
+ return array;
+}
+
+/**
+ * Comparator that returns -1, +1, or 0 depending on whether a &lt; b, or a &gt; b, or
+ * neither, respectively.
+ * @param {object} a
+ * @param {object} b
+ * @return {number} -1, 0, or +1
+ */
+function cmp(a,b) {
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ return 0;
+}
+
+function arrayToSet(arr) {
+ var set = {};
+ arr.forEach(function(x) {
+ set[x] = true;
+ });
+ return set;
+}
+
+function mergeArrays(mergeFunction, a1, a2, etc) {
+ var len = a1.length;
+ var arrays = Array.prototype.slice.call(arguments, 1);
+ for (var i = 0; i < arrays.length; ++i) {
+ if (arrays[i].length != len) {
+ return;
+ }
+ }
+ out = [];
+ for (var i = 0; i < a1.length; ++i) {
+ out.push(mergeFunction.apply(this, arrays.map(function(array) { return array[i]; })));
+ }
+ return out;
+}
+
+function debug(obj) {
+ if (typeof(obj) == 'object') {
+ var ret = [];
+ if (obj) {
+ eachProperty(obj, function(k, v) {
+ ret.push(k+" -> "+debug(v));
+ });
+ return '['+ret.join(", ")+']';
+ } else {
+ return String(obj);
+ }
+ } else {
+ return String(obj);
+ }
+}
+
+/**
+ * Create a scala function out of the given JS function.
+ */
+function scalaFn(nargs, f) {
+ if (typeof(f) == 'function') {
+ return new Packages.scala['Function'+nargs]({
+ apply: f
+ });
+ } else {
+ return new Packages.scala['Function'+nargs]({
+ apply: function() { return f; }
+ })
+ }
+}
+
+function scalaF0(f) {
+ return scalaFn(0, f);
+}
+
+function scalaF1(f) {
+ return scalaFn(1, f);
+}
+
+/**
+ * Some bonus functions for functional programming.
+ */
+function f_curry(thisPtr, f, arg1, arg2, etc) {
+ var curriedArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ return f.apply(thisPtr, curriedArgs.concat(args));
+ }
+}
+
+function f_limitArgs(thisPtr, f, n) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0, n);
+ return f.apply(thisPtr, args);
+ }
+}
+
+
+
diff --git a/trunk/infrastructure/framework-src/modules/netutils.js b/trunk/infrastructure/framework-src/modules/netutils.js
new file mode 100644
index 0000000..6616b76
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/netutils.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview A collection of network-related utilities.
+ */
+
+import("jsutils.eachProperty");
+
+jimport("java.net.InetAddress");
+
+
+function urlPost(url0, params, options) {
+ var url = new java.net.URL(url0);
+
+ var data;
+ if (typeof(params) == 'string') {
+ data = params;
+ } else if (typeof(params) == 'object') {
+ var components = [];
+ eachProperty(params, function(k, v) {
+ components.push(encodeURIComponent(k)+"="+encodeURIComponent(v));
+ });
+ data = components.join('&');
+ }
+ var dataBytes = (new java.lang.String(data)).getBytes("UTF-8");
+ var conn = url.openConnection();
+ conn.setInstanceFollowRedirects(true);
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
+ conn.setRequestProperty("Content-Length", dataBytes.length);
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setConnectTimeout(30*1000);
+ conn.setReadTimeout(30*1000);
+ conn.getOutputStream().write(dataBytes);
+ var content = conn.getContent();
+ var responseCode = conn.getResponseCode();
+ var contentType = conn.getContentType();
+ var contentEncoding = conn.getContentEncoding();
+
+ if ((content instanceof java.io.InputStream) && (new java.lang.String(contentType)).startsWith("text/")) {
+ if (! contentEncoding) {
+ var encoding = contentType.split(/;\s*/);
+ if (encoding.length > 1) {
+ encoding = encoding[1].split("=");
+ if (encoding[0] == "charset")
+ contentEncoding = encoding[1];
+ }
+ }
+ content = net.appjet.common.util.BetterFile.getStreamBytes(content);
+ if (contentEncoding) {
+ content = (new java.lang.String(content, contentEncoding));
+ }
+ }
+
+ return {
+ content: content,
+ status: responseCode,
+ contentType: contentType,
+ contentEncoding: contentEncoding
+ };
+}
+
+function getHostnameFromIp(ip) {
+ var ret = null;
+ try {
+ var addr = InetAddress.getByName(ip);
+ ret = addr.getHostName();
+ } catch (ex) { }
+ return ret;
+}
+
+
+
diff --git a/trunk/infrastructure/framework-src/modules/profiler.js b/trunk/infrastructure/framework-src/modules/profiler.js
new file mode 100644
index 0000000..223c197
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/profiler.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * @fileDescription
+ * Sosme profiling functions.
+ */
+var time = function() {
+ return Packages.net.appjet.oui.profiler.time();
+}
+
+var record = function(op, time) {
+ Packages.net.appjet.oui.profiler.record(op, time);
+}
+
+var recordCumulative = function(op, time) {
+ Packages.net.appjet.oui.profiler.recordCumulative(op, time);
+}
+
+var reset = function() {
+ Packages.net.appjet.oui.profiler.reset();
+}
+
+var print = function() {
+ Packages.net.appjet.oui.profiler.print();
+}
+
+var rcb = function(op, cumulative) {
+ var start = time();
+ return function() {
+ var end = time();
+ (cumulative ? recordCumulative : record)(op, end-start);
+ }
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/modules/sessions.js b/trunk/infrastructure/framework-src/modules/sessions.js
new file mode 100644
index 0000000..3d0041b
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sessions.js
@@ -0,0 +1,156 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("dateutils");
+import("fastJSON");
+import("fileutils");
+import("jsutils.{eachProperty,keys}");
+import("stringutils.{randomHash,startsWith,endsWith}");
+import("sync");
+
+jimport("net.appjet.common.util.ExpiringMapping");
+
+//----------------------------------------------------------------
+
+var _DEFAULT_COOKIE_NAME = "SessionID";
+var _DEFAULT_SERVER_EXPIRATION = 3*24*60*60*1000; // 72 hours
+
+function getSessionId(cookieName, createIfNotPresent, domain) {
+ if (request.isComet || request.isCron) {
+ return null;
+ }
+
+ if (request.cookies[cookieName]) {
+ return request.cookies[cookieName];
+ }
+
+ if (!createIfNotPresent) {
+ return null;
+ }
+
+ // Keep sessionId in requestCache so this function can be called multiple
+ // times per request without multiple calls to setCookie().
+ if (!appjet.requestCache.sessionId) {
+ var sessionId = randomHash(16);
+
+ response.setCookie({
+ name: cookieName,
+ value: sessionId,
+ path: "/",
+ domain: (domain || undefined)
+ });
+
+ appjet.requestCache.sessionId = sessionId;
+ }
+
+ return appjet.requestCache.sessionId;
+}
+
+function _getExpiringSessionMap(db) {
+ sync.callsyncIfTrue(db,
+ function() { return (!db.map); },
+ function() { db.map = new ExpiringMapping(_DEFAULT_SERVER_EXPIRATION); });
+ return db.map;
+}
+
+function _getCachedDb() {
+ return appjet.cacheRoot("net.appjet.ajstdlib.session");
+}
+
+//----------------------------------------------------------------
+
+function getSession(opts) {
+ // Session options.
+ if (!opts) { opts = {}; }
+ var cookieName = opts.cookieName || _DEFAULT_COOKIE_NAME;
+
+ // get cookie ID (sets response cookie if necessary)
+ var sessionId = getSessionId(cookieName, true, opts.domain);
+
+ // get expiring session map
+ var db = _getCachedDb();
+ var map = _getExpiringSessionMap(db);
+
+ // get session data object
+ var domainKey = (opts.domain ? opts.domain : "");
+ var dataKey = [domainKey, sessionId].join('$');
+
+ var sessionData = map.get(dataKey);
+ if (!sessionData) {
+ sessionData = {};
+ map.put(dataKey, sessionData);
+ }
+ else {
+ map.touch(dataKey);
+ }
+
+ return sessionData;
+}
+
+function writeSessionsToDisk() {
+ var dateString = dateutils.dateFormat(new Date(), "yyyy-MM-dd");
+ var dataFile = new Packages.java.io.File(appjet.config.sessionStoreDir+"/sessions-"+dateString+".jslog");
+ dataFile.getParentFile().mkdirs();
+ var writer = new java.io.FileWriter(dataFile);
+ var map = _getCachedDb().map;
+ if (! map) { return; }
+ var keyIterator = map.listAllKeys().iterator();
+ while (keyIterator.hasNext()) {
+ var key = keyIterator.next();
+ var session = map.get(key);
+ if (keys(session).length == 0) { continue; }
+ var obj = { key: key, session: session };
+ var json = fastJSON.stringify(obj);
+ writer.write(json);
+ writer.write("\n");
+ }
+ writer.flush();
+ writer.close();
+}
+
+function _extractDate(fname) {
+ var datePart = fname.substr("sessions-".length, "2009-09-24".length);
+ return Number(datePart.split("-").join(""));
+}
+
+function readLatestSessionsFromDisk() {
+ var dir = new Packages.java.io.File(appjet.config.sessionStoreDir);
+ if (! dir.exists()) { return; }
+ var files = dir.listFiles(new Packages.java.io.FilenameFilter({
+ accept: function(dir, name) {
+ return startsWith(name, "sessions") && endsWith(name, ".jslog")
+ }
+ }));
+ if (files.length == 0) { return; }
+ var latestFile = files[0];
+ for (var i = 1; i < files.length; ++i) {
+ if (_extractDate(files[i].getName()) > _extractDate(latestFile.getName())) {
+ latestFile = files[i];
+ }
+ }
+ var map = _getExpiringSessionMap(_getCachedDb());
+ fileutils.eachFileLine(latestFile, function(json) {
+ try {
+ var obj = fastJSON.parse(json);
+ var key = obj.key;
+ var session = obj.session;
+ map.put(key, session);
+ } catch (err) {
+ Packages.java.lang.System.out.println("Error reading sessions file on line '"+json+"': "+String(err));
+ }
+ });
+ latestFile.renameTo(new Packages.java.io.File(latestFile.getParent()+"/used-"+latestFile.getName()));
+}
diff --git a/trunk/infrastructure/framework-src/modules/sqlbase/persistent_vars.js b/trunk/infrastructure/framework-src/modules/sqlbase/persistent_vars.js
new file mode 100644
index 0000000..1c4cc95
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sqlbase/persistent_vars.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import("sqlbase.sqlobj");
+import("sqlbase.sqlcommon");
+
+jimport("java.lang.System.out.println");
+
+// TODO: add caching?
+
+// Curently supports:
+// Strings
+
+function get(name) {
+ if (!sqlcommon.doesTableExist('persistent_vars')) {
+ return undefined;
+ }
+ var r = sqlobj.selectSingle('persistent_vars', {name: name});
+ if (!r) {
+ return undefined;
+ }
+ return r.stringVal;
+}
+
+function put(name, val) {
+ if (typeof(val) != 'string') {
+ throw Error("unsupported type for persistent_vars: "+typeof(val));
+ }
+
+ var r = sqlobj.selectSingle('persistent_vars', {name: name});
+ if (r) {
+ sqlobj.updateSingle('persistent_vars', {id: r.id}, {stringVal: val});
+ } else {
+ sqlobj.insert('persistent_vars', {name: name, stringVal: val});
+ }
+}
+
+function remove(name) {
+ var r = sqlobj.selectSingle('persistent_vars', {name: name});
+ if (r) {
+ sqlobj.deleteRows('persistent_vars', {id: r.id});
+ }
+}
diff --git a/trunk/infrastructure/framework-src/modules/sqlbase/sqlbase.js b/trunk/infrastructure/framework-src/modules/sqlbase/sqlbase.js
new file mode 100644
index 0000000..3df1a0f
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sqlbase/sqlbase.js
@@ -0,0 +1,205 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("jsutils.*");
+import("sqlbase.sqlcommon");
+import("fastJSON");
+import("timer");
+
+jimport("java.lang.System.out.println");
+
+function _sqlbase() {
+ return sqlcommon.getSqlBase();
+}
+
+/**
+ * Creates a SQL table suitable for storing a mapping from String to JSON value.
+ * Maximum key length is 128 characters. Has no effect if the table already exists.
+ */
+function createJSONTable(tableName) {
+ _sqlbase().createJSONTable(String(tableName));
+}
+
+/**
+ * Retrieves a JavaScript object or value from a table. Returns undefined
+ * if there is no mapping for the given string key. Requires that the table
+ * exist.
+ */
+function getJSON(tableName, stringKey) {
+ var result = _sqlbase().getJSON(String(tableName), String(stringKey));
+ if (result) {
+
+ return fastJSON.parse(String(result))['x'];
+
+ /* performance-testing JSON
+ var obj1 = timer.time("JSON.parse (json2)", function() {
+ return JSON.parse(String(result))['x'];
+ });
+ var obj2 = timer.time("JSON.parse (fastJSON)", function() {
+ return fastJSON.parse(String(result))['x'];
+ });
+ return obj2;
+ */
+ }
+ return undefined;
+}
+
+function getAllJSON(tableName, start, count) {
+ var result = _sqlbase().getAllJSON(String(tableName), Number(start), Number(count));
+ return Array.prototype.map.call(result, function(x) {
+ return {id: x.id(), value: fastJSON.parse(String(x.value()))['x']};
+ })
+}
+
+function getAllJSONKeys(tableName) {
+ var result = _sqlbase().getAllJSONKeys(String(tableName));
+ return Array.prototype.map.call(result, function(x) { return String(x); });
+}
+
+/**
+ * Assigns a JavaScript object or primitive value to a string key in a table.
+ * Maximum key length is 128 characters. Requires that the table exist.
+ */
+function putJSON(tableName, stringKey, objectOrValue) {
+ var obj = ({x:objectOrValue});
+
+ var json = fastJSON.stringify(obj);
+
+ /* performance-testing JSON
+
+ var json1 = timer.time("JSON.stringify (json2)", function() {
+ return JSON.stringify(obj);
+ });
+ var json2 = timer.time("JSON.stringify (fastJSON)", function() {
+ return fastJSON.stringify(obj);
+ });
+
+ if (json1 != json2) {
+ println("json strings do not match!");
+ println("\n\n");
+ println(json1);
+ println("\n");
+ println(json2);
+ println("\n\n");
+ }*/
+
+ _sqlbase().putJSON(String(tableName), String(stringKey), json);
+}
+
+/**
+ * Removes the mapping for a string key from a table. Requires that the table
+ * exist.
+ */
+function deleteJSON(tableName, stringKey) {
+ _sqlbase().deleteJSON(String(tableName), String(stringKey));
+}
+
+/**
+ * Creates a SQL table suitable for storing a mapping from (key,n) to string.
+ * The mapping may be sparse, but storage is most efficient when n are consecutive.
+ * The "length" of the array is not stored and must be externally maintained.
+ * Maximum key length is 128 characters. This call has no effect if the table
+ * already exists.
+ */
+function createStringArrayTable(tableName) {
+ _sqlbase().createStringArrayTable(String(tableName));
+}
+
+/**
+ * Assigns a string value to a (key,n) pair in a StringArray table. Maximum key length
+ * is 128 characters. Requires that the table exist.
+ */
+function putStringArrayElement(tableName, stringKey, n, value) {
+ _sqlbase().putStringArrayElement(String(tableName), String(stringKey),
+ Number(n), String(value));
+}
+
+/**
+ * Equivalent to a series of consecutive puts of the elements of valueArray, with the first
+ * one going to n=startN, the second to n=startN+1, and so on, but much more efficient.
+ */
+function putConsecutiveStringArrayElements(tableName, stringKey, startN, valueArray) {
+ var putter = _sqlbase().putMultipleStringArrayElements(String(tableName), String(stringKey));
+ for(var i=0;i<valueArray.length;i++) {
+ putter.put(Number(startN)+i, String(valueArray[i]));
+ }
+ putter.finish();
+}
+
+/**
+ * Equivalent to a series of puts of the (key,value) entries of the JavaScript object
+ * nToValue, using as few database operations as possible.
+ */
+function putDictStringArrayElements(tableName, stringKey, nToValue) {
+ var nArray = [];
+ for(var n in nToValue) {
+ nArray.push(n);
+ }
+ nArray.sort(function(a,b) { return Number(a) - Number(b); });
+
+ var putter = _sqlbase().putMultipleStringArrayElements(String(tableName), String(stringKey));
+ nArray.forEach(function(n) {
+ putter.put(Number(n), String(nToValue[n]));
+ });
+ putter.finish();
+}
+
+/**
+ * Retrieves a string value from a StringArray table. Returns undefined
+ * if there is no mapping for the given (key,n) pair. Requires that the table
+ * exist.
+ */
+function getStringArrayElement(tableName, stringKey, n) {
+ var result = _sqlbase().getStringArrayElement(String(tableName),
+ String(stringKey), Number(n));
+ if (result) {
+ return String(result);
+ }
+ return undefined;
+}
+
+/**
+ * Retrieves all values from the database page that contains the mapping for n.
+ * Properties are added to destMap for n, if present in the database, and any other
+ * numeric entries in the same page. No return value.
+ */
+function getPageStringArrayElements(tableName, stringKey, n, destMap) {
+ var array = _sqlbase().getPageStringArrayElements(String(tableName), String(stringKey), n);
+ for(var i=0;i<array.length;i++) {
+ var entry = array[i];
+ destMap[entry.index()] = String(entry.value());
+ }
+}
+
+/**
+ * Removes the mapping for a (key,n) pair from a StringArray table. Requires that the table
+ * exist.
+ */
+function deleteStringArrayElement(tableName, stringKey, n) {
+ _sqlbase().putStringArrayElement(String(tableName), String(stringKey), Number(n), null);
+}
+
+/**
+ * Removes all mappings and metadata associated with a given key in a table.
+ */
+function clearStringArray(tableName, stringKey) {
+ _sqlbase().clearStringArray(String(tableName), stringKey);
+}
+
+function getStringArrayAllKeys(tableName) {
+ var result = _sqlbase().getStringArrayAllKeys(String(tableName));
+ return Array.prototype.map.call(result, function(x) { return String(x); });
+}
diff --git a/trunk/infrastructure/framework-src/modules/sqlbase/sqlcommon.js b/trunk/infrastructure/framework-src/modules/sqlbase/sqlcommon.js
new file mode 100644
index 0000000..360f5e2
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sqlbase/sqlcommon.js
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("jsutils.scalaF1")
+import("stringutils.startsWith");
+
+jimport("net.appjet.ajstdlib.SQLBase");
+jimport("java.lang.System.out.println");
+
+function _sqlbase() { return appjet.cache.sqlbase };
+
+function init(driver, url, username, password) {
+ var dbName = url.split(":")[1];
+ println("Using "+dbName+" database type.");
+
+ appjet.cache.sqlbase = new SQLBase(driver, url, username, password);
+
+ // Test the connection
+ println("Establishing "+dbName+" connection (this may take a minute)...");
+ try {
+ withConnection(function() {
+ return;
+ });
+ } catch (ex) {
+ println("Error establishing "+dbName+" connection:");
+ println(ex.toString().split('\n')[0]);
+ if (_sqlbase().isMysql()) {
+ println("Perhaps mysql server is not running, or you did not specify "+
+ "proper database credentials with --etherpad.SQL_PASSWORD "+
+ "and --etherpad.SQL_USERNAME?");
+ }
+ if (_sqlbase().isDerby()) {
+ println("Perhaps database directory "+appjet.config.derbyHome+
+ " is not writable?");
+ }
+ println("Exiting...");
+ Packages.java.lang.System.exit(1);
+ }
+ println(dbName+" connection established.");
+}
+
+function onShutdown() {
+ _sqlbase().close();
+}
+
+function withConnection(f) {
+ return _sqlbase().withConnection(scalaF1(f));
+}
+
+function inTransaction(f) {
+ return _sqlbase().inTransaction(scalaF1(f));
+}
+
+function closing(s, f) {
+ if (s instanceof java.sql.Connection) {
+ throw new java.lang.IllegalArgumentException("Don't want to use 'closing()' on a sql connection!");
+ }
+ try {
+ return f();
+ }
+ finally {
+ s.close();
+ }
+}
+
+function doesTableExist(table) {
+ return withConnection(function(conn) {
+ return _sqlbase().doesTableExist(conn, table);
+ });
+}
+
+function autoIncrementClause() {
+ return _sqlbase().autoIncrementClause();
+}
+
+function createTableOptions() {
+ return _sqlbase().createTableOptions();
+}
+
+function btquote(x) { return _sqlbase().quoteIdentifier(x); }
+
+function getSqlBase() { return _sqlbase(); }
+
+function isMysql() { return _sqlbase().isMysql(); }
+function isDerby() { return _sqlbase().isDerby(); }
+
diff --git a/trunk/infrastructure/framework-src/modules/sqlbase/sqlobj.js b/trunk/infrastructure/framework-src/modules/sqlbase/sqlobj.js
new file mode 100644
index 0000000..4bc1263
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sqlbase/sqlobj.js
@@ -0,0 +1,505 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("cache_utils.syncedWithCache");
+import("sqlbase.sqlcommon.*");
+import("jsutils.*");
+
+jimport("java.lang.System.out.println");
+jimport("java.sql.Statement");
+
+function _withCache(name, fn) {
+ return syncedWithCache('sqlobj.'+name, fn);
+}
+
+function getIdColspec() {
+ return ('INT NOT NULL '+autoIncrementClause()+' PRIMARY KEY');
+}
+
+function getLongtextColspec(extra) {
+ var spec = getSqlBase().longTextType();
+ if (extra) {
+ spec = (spec + " " + extra);
+ }
+ return spec;
+}
+
+function getBoolColspec(extra) {
+ var spec;
+ if (isMysql()) {
+ spec = 'TINYINT(1)';
+ } else {
+ spec = 'SMALLINT';
+ }
+ if (extra) {
+ spec = (spec + " " + extra);
+ }
+ return spec;
+}
+
+function getDateColspec(extra) {
+ var spec;
+ if (isMysql()) {
+ spec = 'DATETIME';
+ } else {
+ spec = 'TIMESTAMP';
+ }
+ if (extra) {
+ spec = (spec + " " + extra);
+ }
+ return spec;
+}
+
+function _bq(x) { return btquote(x); }
+
+/*
+ * for debugging queries
+ */
+function _qdebug(q) {
+ if (appjet.config.debugSQL) {
+ println(q);
+ }
+}
+
+/** executeFn is either "execute" or "executeUpdate" "executeQuery" */
+function _execute(stmnt, executeFn) {
+ if (!executeFn) {
+ executeFn = 'execute';
+ }
+ return withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt);
+ return closing(pstmnt, function() {
+ _qdebug(stmnt);
+ return pstmnt[executeFn]();
+ });
+ });
+}
+
+function _executeUpdate(stmnt) {
+ return _execute(stmnt, 'executeUpdate');
+}
+
+function _executeQuery(stmnt) {
+ return _execute(stmnt, 'executeQuery');
+}
+
+/*
+ * Not all SQL/JS types supported.
+ */
+function _getJsValFromResultSet(rs, type, colName) {
+ var r;
+ if (type == java.sql.Types.VARCHAR ||
+ type == java.sql.Types.LONGVARCHAR ||
+ type == java.sql.Types.CHAR) {
+ r = String(rs.getString(colName));
+ } else if (type == java.sql.Types.TIMESTAMP) {
+ var t = rs.getTimestamp(colName);
+ if (t) {
+ r = new Date(t.getTime());
+ } else {
+ r = null;
+ }
+ } else if (type == java.sql.Types.INTEGER ||
+ type == java.sql.Types.SMALLINT ||
+ type == java.sql.Types.TINYINT) {
+ r = rs.getInt(colName);
+ } else if (type == java.sql.Types.BIT) {
+ r = rs.getBoolean(colName);
+ } else {
+ throw Error("Cannot fetch sql type ID "+type+" (columnName = "+colName+")");
+ }
+
+ if (rs.wasNull()) {
+ r = null;
+ }
+ return r;
+}
+
+function _lookupColumnType(tableName, columnName) {
+ return withConnection(function(conn) {
+ var metadata = conn.getMetaData();
+ var rs = metadata.getColumns(null, null, tableName, columnName);
+ if (!rs) {
+ throw Error("Table '"+tableName+"' does not appear to have colum '"+columnName+"'.");
+ }
+ var rsmd = rs.getMetaData();
+ var colCount = rsmd.getColumnCount();
+// rs.first();
+ rs.next();
+ var type = rs.getInt("DATA_TYPE");
+ return type;
+ });
+}
+
+/* cached, on misses calls _lookuParameterType */
+function _getParameterType(tableName, columnName) {
+ var key = [tableName, columnName].join(".");
+ return _withCache('column-types', function(cache) {
+ if (!cache[key]) {
+ cache[key] = _lookupColumnType(tableName, columnName);
+ }
+ return cache[key];
+ });
+}
+
+/*
+ * Not all SQL/JS types supported.
+ */
+function _setPreparedValues(tableName, pstmnt, keyList, obj, indexOffset) {
+ if (!indexOffset) { indexOffset = 0; }
+
+ for (var i = 1; i <= keyList.length; i++) {
+ var k = keyList[i-1];
+ var v = obj[k];
+ var j = i + indexOffset;
+
+ if (v === undefined) {
+ throw Error("value is undefined for key "+k);
+ }
+
+ if (v === null) {
+ var type = _getParameterType(tableName, k);
+ pstmnt.setNull(j, type);
+ } else if (typeof(v) == 'string') {
+ pstmnt.setString(j, v);
+ } else if (typeof(v) == 'number') {
+ pstmnt.setInt(j, v);
+ } else if (typeof(v) == 'boolean') {
+ pstmnt.setBoolean(j, v);
+ } else if (v.valueOf && v.getDate && v.getHours) {
+ pstmnt.setTimestamp(j, new java.sql.Timestamp(+v));
+ } else {
+ throw Error("Cannot insert this type of javascript object: "+typeof(v)+" (key="+k+", value = "+v+")");
+ }
+ }
+}
+
+function _resultRowToJsObj(resultSet) {
+ var resultObj = {};
+
+ var metaData = resultSet.getMetaData();
+ var colCount = metaData.getColumnCount();
+ for (var i = 1; i <= colCount; i++) {
+ var colName = metaData.getColumnName(i);
+ var type = metaData.getColumnType(i);
+ resultObj[colName] = _getJsValFromResultSet(resultSet, type, colName);
+ }
+
+ return resultObj;
+}
+
+/*
+ * Inserts the object into the given table, and returns auto-incremented ID if any.
+ */
+function insert(tableName, obj) {
+ var keyList = keys(obj);
+
+ var stmnt = "INSERT INTO "+_bq(tableName)+" (";
+ stmnt += keyList.map(function(k) { return _bq(k); }).join(', ');
+ stmnt += ") VALUES (";
+ stmnt += keyList.map(function(k) { return '?'; }).join(', ');
+ stmnt += ")";
+
+ return withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt, Statement.RETURN_GENERATED_KEYS);
+ return closing(pstmnt, function() {
+ _setPreparedValues(tableName, pstmnt, keyList, obj, 0);
+ _qdebug(stmnt);
+ pstmnt.executeUpdate();
+ var rs = pstmnt.getGeneratedKeys();
+ if (rs != null) {
+ return closing(rs, function() {
+ if (rs.next()) {
+ return rs.getInt(1);
+ }
+ });
+ }
+ });
+ });
+};
+
+/*
+ * Selects a single object given the constraintMap. If there are more
+ * than 1 objects that match, it will return a single one of them
+ * (unspecified which one). If no objects match, returns null.
+ *
+ * constraints is a javascript object of column names to values.
+ * Currently only supports string equality of constraints.
+ */
+function selectSingle(tableName, constraints) {
+ var keyList = keys(constraints);
+
+ var stmnt = "SELECT * FROM "+_bq(tableName)+" WHERE (";
+ stmnt += keyList.map(function(k) { return '('+_bq(k)+' = '+'?)'; }).join(' AND ');
+ stmnt += ')';
+ if (isMysql()) {
+ stmnt += ' LIMIT 1';
+ }
+
+ return withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt);
+ return closing(pstmnt, function() {
+ _setPreparedValues(tableName, pstmnt, keyList, constraints, 0);
+ _qdebug(stmnt);
+ var resultSet = pstmnt.executeQuery();
+ return closing(resultSet, function() {
+ if (!resultSet.next()) {
+ return null;
+ }
+ return _resultRowToJsObj(resultSet);
+ });
+ });
+ });
+}
+
+function _makeConstraintString(key, value) {
+ if (typeof(value) != 'object' || ! (value instanceof Array)) {
+ return '('+_bq(key)+' = ?)';
+ } else {
+ var comparator = value[0];
+ return '('+_bq(key)+' '+comparator+' ?)';
+ }
+}
+
+function _preparedValuesConstraints(constraints) {
+ var c = {};
+ eachProperty(constraints, function(k, v) {
+ c[k] = (typeof(v) != 'object' || ! (v instanceof Array) ? v : v[1]);
+ });
+ return c;
+}
+
+function selectMulti(tableName, constraints, options) {
+ if (!options) {
+ options = {};
+ }
+
+ var constraintKeys = keys(constraints);
+
+ var stmnt = "SELECT * FROM "+_bq(tableName)+" ";
+
+ if (constraintKeys.length > 0) {
+ stmnt += "WHERE (";
+ stmnt += constraintKeys.map(function(key) {
+ return _makeConstraintString(key, constraints[key]);
+ }).join(' AND ');
+ stmnt += ')';
+ }
+
+ if (options.orderBy) {
+ var orderEntries = [];
+ options.orderBy.split(",").forEach(function(orderBy) {
+ var asc = "ASC";
+ if (orderBy.charAt(0) == '-') {
+ orderBy = orderBy.substr(1);
+ asc = "DESC";
+ }
+ orderEntries.push(_bq(orderBy)+" "+asc);
+ });
+ stmnt += " ORDER BY "+orderEntries.join(", ");
+ }
+
+ if (options.limit) {
+ stmnt += " LIMIT "+options.limit;
+ }
+
+ return withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt);
+ return closing(pstmnt, function() {
+ _setPreparedValues(
+ tableName, pstmnt, constraintKeys,
+ _preparedValuesConstraints(constraints), 0);
+
+ _qdebug(stmnt);
+ var resultSet = pstmnt.executeQuery();
+ var resultArray = [];
+
+ return closing(resultSet, function() {
+ while (resultSet.next()) {
+ resultArray.push(_resultRowToJsObj(resultSet));
+ }
+
+ return resultArray;
+ });
+ });
+ });
+}
+
+/* returns number of rows updated */
+function update(tableName, constraints, obj) {
+ var objKeys = keys(obj);
+ var constraintKeys = keys(constraints);
+
+ var stmnt = "UPDATE "+_bq(tableName)+" SET ";
+ stmnt += objKeys.map(function(k) { return ''+_bq(k)+' = ?'; }).join(', ');
+ stmnt += " WHERE (";
+ stmnt += constraintKeys.map(function(k) { return '('+_bq(k)+' = ?)'; }).join(' AND ');
+ stmnt += ')';
+
+ return withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt);
+ return closing(pstmnt, function() {
+ _setPreparedValues(tableName, pstmnt, objKeys, obj, 0);
+ _setPreparedValues(tableName, pstmnt, constraintKeys, constraints, objKeys.length);
+ _qdebug(stmnt);
+ return pstmnt.executeUpdate();
+ });
+ });
+}
+
+function updateSingle(tableName, constraints, obj) {
+ var count = update(tableName, constraints, obj);
+ if (count != 1) {
+ throw Error("save count != 1. instead, count = "+count);
+ }
+}
+
+function deleteRows(tableName, constraints) {
+ var constraintKeys = keys(constraints);
+ var stmnt = "DELETE FROM "+_bq(tableName)+" WHERE (";
+ stmnt += constraintKeys.map(function(k) { return '('+_bq(k)+' = ?)'; }).join(' AND ');
+ stmnt += ')';
+ withConnection(function(conn) {
+ var pstmnt = conn.prepareStatement(stmnt);
+ closing(pstmnt, function() {
+ _setPreparedValues(tableName, pstmnt, constraintKeys, constraints);
+ _qdebug(stmnt);
+ pstmnt.executeUpdate();
+ });
+ })
+}
+
+//----------------------------------------------------------------
+// table management
+//----------------------------------------------------------------
+
+/*
+ * Create a SQL table, specifying column names and types with a
+ * javascript object.
+ */
+function createTable(tableName, colspec, indices) {
+ if (doesTableExist(tableName)) {
+ return;
+ }
+
+ var stmnt = "CREATE TABLE "+_bq(tableName)+ " (";
+ stmnt += keys(colspec).map(function(k) { return (_bq(k) + ' ' + colspec[k]); }).join(', ');
+ if (indices) {
+ stmnt += ', ' + keys(indices).map(function(k) { return 'INDEX (' + _bq(k) + ')'; }).join(', ');
+ }
+ stmnt += ')'+createTableOptions();
+ _execute(stmnt);
+}
+
+function dropTable(tableName) {
+ _execute("DROP TABLE "+_bq(tableName));
+}
+
+function dropAndCreateTable(tableName, colspec, indices) {
+ if (doesTableExist(tableName)) {
+ dropTable(tableName);
+ }
+
+ return createTable(tableName, colspec, indices);
+}
+
+function renameTable(oldName, newName) {
+ _executeUpdate("RENAME TABLE "+_bq(oldName)+" TO "+_bq(newName));
+}
+
+function modifyColumn(tableName, columnName, newSpec) {
+ _executeUpdate("ALTER TABLE "+_bq(tableName)+" MODIFY "+_bq(columnName)+" "+newSpec);
+}
+
+function alterColumn(tableName, columnName, alteration) {
+ var q = "ALTER TABLE "+_bq(tableName)+" ALTER COLUMN "+_bq(columnName)+" "+alteration;
+ _executeUpdate(q);
+}
+
+function changeColumn(tableName, columnName, newSpec) {
+ var q = ("ALTER TABLE "+_bq(tableName)+" CHANGE COLUMN "+_bq(columnName)
+ +" "+newSpec);
+ _executeUpdate(q);
+}
+
+function addColumns(tableName, colspec) {
+ inTransaction(function(conn) {
+ eachProperty(colspec, function(name, definition) {
+ var stmnt = "ALTER TABLE "+_bq(tableName)+" ADD COLUMN "+_bq(name)+" "+definition;
+ _executeUpdate(stmnt);
+ });
+ });
+}
+
+function dropColumn(tableName, columnName) {
+ var stmnt = "ALTER TABLE "+_bq(tableName)+" DROP COLUMN "+_bq(columnName);
+ _executeUpdate(stmnt);
+}
+
+function listTables() {
+ return withConnection(function(conn) {
+ var metadata = conn.getMetaData();
+ var resultSet = metadata.getTables(null, null, null, null);
+ var resultArray = [];
+
+ return closing(resultSet, function() {
+ while (resultSet.next()) {
+ resultArray.push(resultSet.getString("TABLE_NAME"));
+ }
+ return resultArray;
+ });
+ });
+}
+
+function setTableEngine(tableName, engineName) {
+ var stmnt = "ALTER TABLE "+_bq(tableName)+" ENGINE="+_bq(engineName);
+ _executeUpdate(stmnt);
+}
+
+function getTableEngine(tableName) {
+ if (!isMysql()) {
+ throw Error("getTableEngine() only supported by MySQL database type.");
+ }
+
+ var tableEngines = {};
+
+ withConnection(function(conn) {
+ var stmnt = "show table status";
+ var pstmnt = conn.prepareStatement(stmnt);
+ closing(pstmnt, function() {
+ _qdebug(stmnt);
+ var resultSet = pstmnt.executeQuery();
+ closing(resultSet, function() {
+ while (resultSet.next()) {
+ var n = resultSet.getString("Name");
+ var eng = resultSet.getString("Engine");
+ tableEngines[n] = eng;
+ }
+ });
+ });
+ });
+
+ return tableEngines[tableName];
+}
+
+function createIndex(tableName, columns) {
+ var indexName = "idx_"+(columns.join("_"));
+ var stmnt = "CREATE INDEX "+_bq(indexName)+" on "+_bq(tableName)+" (";
+ stmnt += columns.map(_bq).join(", ");
+ stmnt += ")";
+ _executeUpdate(stmnt);
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/stringutils.js b/trunk/infrastructure/framework-src/modules/stringutils.js
new file mode 100644
index 0000000..3fe5611
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/stringutils.js
@@ -0,0 +1,399 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileOverview A collection of various string utilities.
+ */
+
+// TODO: uncomment with import works with *
+
+import("funhtml.{TABLE,TR,TH,TD,OL,LI}");
+import("jsutils.{object,eachProperty}");
+
+//import("funhtml.*");
+
+jimport("java.util.Random");
+jimport("java.lang.System.currentTimeMillis");
+
+
+/**
+ * Removes leading and trailing whitespace from a string.
+ * @param {string} str
+ * @return {string} The trimmed string.
+ */
+function trim(str) {
+ return str.replace(/^\s+|\s+$/g, "");
+}
+
+//----------------------------------------------------------------
+// String prototype enhancements.
+// TODO: should we move this to a new library "enhancedstring"?
+//----------------------------------------------------------------
+startsWith = function(s, prefix) {
+ return (s.indexOf(prefix) == 0);
+};
+endsWith = function(s, suffix) {
+ return (s.substr(s.length - suffix.length) == suffix);
+};
+contains = function(s, x) {
+ return (s.indexOf(x) != -1);
+};
+makeTitle = function(s) {
+ if (! s) return;
+ return s.split(" ").map(function(x) {
+ return x[0].toUpperCase() + x.substr(1)
+ }).join(" ");
+}
+repeat = function(s, n) {
+ var out = [];
+ while (n-- > 0) {
+ out.push(s);
+ }
+ return out.join('');
+}
+
+/*
+ * Helper function that converts a raw string to an HTML string, with
+ * character entities replaced by appropriate HTML codes, and newlines
+ * rentered as BRs.
+ *
+ * <p>A more general version of this function is toHTML(), which can operate
+ * on not just strings, but any object.
+ *
+ * @param {string} str the raw string
+ * @return {string} HTML-formatted string
+ */
+function _stringToHTML(str) {
+ return String(net.appjet.oui.Util.stringToHTML(str));
+}
+
+// used to convert an object to HTML when the object does not have a
+// toHTML method.
+//
+function _coerceObjectToHTML(obj) {
+ var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0});
+ eachProperty(obj, function(name, value) {
+ t.push(TR(TH(String(name)), TD(String(value))));
+ });
+ return toHTML(t);
+}
+
+// Converts an array to an HTML list by listing its properties and
+// recursively converting the values to HTML by calling toHTML() on
+// each of them.
+function _objectToOL(obj) {
+ var l = OL();
+ eachProperty(obj, function(name, value) {
+ l.push(LI({value: name}, value));
+ });
+ return l;
+}
+
+function _sameProperties(obj1, obj2) {
+ if (typeof(obj1) != 'object' || typeof(obj2) != 'object')
+ return typeof(obj1) == typeof(obj2);
+
+ var mismatch = 0;
+ eachProperty(obj1, function(name) {
+ if (! obj2.hasOwnProperty(name)) {
+ mismatch++;
+ }});
+ eachProperty(obj2, function(name) {
+ if (! obj1.hasOwnProperty(name)) {
+ mismatch++;
+ }});
+ return mismatch < 2;
+}
+
+//
+// for pretty-printing arrays. needs a lot of work.
+//
+function _arrayToHTML(a) {
+ if (a.length === 0) {
+ return "";
+ }
+ if (typeof(a[0]) != 'object') {
+ return toHTML(_objectToOL(a));
+ } else if (! _sameProperties(a[0], a[1])) {
+ return toHTML(_objectToOL(a));
+ } else {
+ return _likeObjectsToHTML(function (f) {
+ a.forEach(function(value, i) {
+ f({index: i}, value, {});
+ });}, null);
+ }
+}
+
+/** @ignore */
+
+// a foreaching function that takes three arguments: properties to put first,
+// properties to put in the middle, and properties to put at the end.
+// and a table header (with large colspan)
+function _likeObjectsToHTML(forEachFunction, tophead) {
+ objs = [];
+ prepnames = new StringSet();
+ objpnames = new StringSet();
+ postpnames = new StringSet();
+ rows = [];
+
+ var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0});
+ var head = TR();
+ if (tophead)
+ t.push(tophead);
+ t.push(head);
+
+ var butWaitTheresMore = false;
+ var howManyMore = 0;
+
+ forEachFunction(function(pre, o, post) {
+ if (objs.length >= 10) {
+ butWaitTheresMore = true;
+ howManyMore++;
+ return;
+ }
+ objs.push({pre: pre, o: o, post: post});
+ var tr = TR();
+ rows.push(tr);
+ t.push(tr);
+
+ eachProperty(pre, function(name) { prepnames.add(name); });
+ eachProperty(o, function(name) { objpnames.add(name); });
+ eachProperty(post, function(name) { postpnames.add(name); });
+ });
+ var numpnames = 0;
+ var appendTDsForPropName = function (where) {
+ return function(name) {
+ numpnames++;
+ head.push(TH(name));
+ for (var j = 0; j < objs.length; ++j) {
+ if (! (objs[j][where] === undefined) && ! (objs[j][where][name] === undefined))
+ rows[j].push(TD(String(objs[j][where][name])));
+ else
+ rows[j].push(TD());
+ }
+ };
+ };
+ prepnames.forEach(appendTDsForPropName("pre"));
+ objpnames.forEach(appendTDsForPropName("o"));
+ postpnames.forEach(appendTDsForPropName("post"));
+ if (butWaitTheresMore) {
+ t.push(TR(TD({colspan: numpnames}, "..."+howManyMore+
+ " additional element"+(howManyMore == 1 ? "" : "s")+" omitted...")));
+ }
+ return toHTML(t);
+}
+
+/**
+ * Returns a string with any number of variables substituted in, as
+ * popularized by C's function of the same name. Some common substitutions:
+ *
+ * <ul><li>%d - an integer</li><li>%f - a floating-point number</li><li>%b - a boolean</li>
+ * <li>%s - a string</li></ul>
+ *
+ * <p>Each time one of these "slot" appears in your format string, the next argument is displayed
+ * according to the type of slot you specified.
+ *
+ * <p>AppJet supports <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/Formatter.html">
+ * Java's specification of printf</a>, which has a ton of features, including selecting
+ * arguments out of order, formatting dates and times, and specifying how many characters
+ * wide each slot should be.
+ *
+ * @example
+var x = 5;
+response.write(sprintf("an integer: %d", x));
+response.write(sprintf("Two strings: [%s] and [%s].", "string one", "string two"));
+ *
+ * @param {string} formatString
+ * @param {*} arg1
+ * @param {*} arg2
+ * @param {*} arg3 ...
+ */
+function sprintf(formatString, arg1, arg2, etc) {
+ if (typeof(formatString) != 'string') {
+ throw new Error('printf takes a string as the first argument.');
+ }
+ var argList = java.lang.reflect.Array.newInstance(java.lang.Object, arguments.length-1);
+ for (var i = 1; i < arguments.length; i++) {
+ if (arguments[i] instanceof Date)
+ argList[i-1] = arguments[i].getTime();
+ else
+ argList[i-1] = arguments[i];
+ }
+ return String(net.appjet.ajstdlib.printf.printf(formatString, argList));
+};
+
+/**
+ * Replaces keys of data found in string with their corresponding values.
+ *
+ * <p>(Inspired by http://javascript.crockford.com/remedial.html)
+ *
+ * @example
+var data = {name: "Aaron", age: 25, today: new Date()};
+print(supplant(data, """
+
+{name}'s age is {age} years, as of {today}.
+
+"""));
+
+ * @param {object} data dictionary of values
+ * @param {string} str
+ * @return {string} str with keys of data replaced by their values
+ */
+function supplant(data, str) {
+ var s = str;
+ var o = data;
+ function rep(a, b) {
+ var r = o[b];
+ if (typeof(r) != 'undefined') {
+ return r;
+ } else {
+ return a;
+ }
+ }
+ return s.replace(/{([^{}]*)}/g, rep);
+};
+
+//----------------------------------------------------------------
+// raw printing
+//----------------------------------------------------------------
+var _raw_prototype;
+
+/**
+ * Used for printing un-escaped HTML, such as your own HTML tags.
+ *
+ * <p>Normally, printing a string will cause it to be translated
+ * so that it appears the same on the screen as it did in your code.
+ * If you're writing your own HTML, you don't want it to be processed
+ * this way. Wrapping a string in html(...) by-passes normal printing behavior,
+ * so that print(html(" -- html goes here ---")) will write the HTML
+ * directly to the page.
+ *
+ * <p>If you want to mix your own HTML code with HTML code generated from a
+ * tag object, you can get the HTML for the tag by calling its toHTML(...) method.
+ *
+ * <p>Multiple arguments to html(...) will be concatenated into one string.
+ *
+ * @example
+print(html("""
+&lt;br /&gt;
+&lt;br /&gt;
+&lt;div&gt;&lt;p&gt;Here is some text inside a P inside a DIV.&lt;/p&gt;
+&lt;/div&gt;
+&lt;br /&gt;
+"""));
+ *
+ * @param {string} text the raw text
+ * @return {object} an object which, when printed, prints the raw html text
+ */
+function html(text) {
+ if (!_raw_prototype) {
+ _raw_prototype = object(Object.prototype);
+ _raw_prototype.toString = function() { return this._text; };
+ _raw_prototype.toHTML = function() { return this._text; };
+ }
+ var rawObj = object(_raw_prototype);
+ rawObj._text = Array.prototype.map.call(arguments, String).join('');
+ return rawObj;
+}
+
+/**
+ * This function is used by print(...) to convert a string or object
+ * into nice-looking printable HTML. It may be useful in conjunction
+ * with html(...) if you wish to work directly with HTML.
+ *
+ * <p>You can control how toHTML(...) (and therefore print(...)) behave on an object
+ * by giving that object a .toHTML() function.
+ *
+ * @param {*} x any javascript variable
+ * @return {string} html-formatted string
+ */
+function toHTML(x) {
+ if (typeof(x) == 'undefined') {
+ return 'undefined';
+ }
+ if (x === null) {
+ return 'null';
+ }
+ if (typeof x == "string" || (x instanceof java.lang.String)) {
+ return _stringToHTML(x);
+ }
+ if (typeof(x.toHTML) == "function") {
+ return x.toHTML();
+ }
+ if (typeof(x) == "xml") {
+ return _stringToHTML(x.toSource());
+ }
+ if (x instanceof Array) {
+ return _arrayToHTML(x);
+ }
+ if (x instanceof Date) {
+ var pieces = x.toString().split(" ");
+ return pieces.slice(0, 5).join(' ') + ' ' + pieces[6];
+ }
+ if (typeof(x) == "object") {
+ return _coerceObjectToHTML(x);
+ }
+ // TODO: add more types to auto-printing, such as functions,
+ // numbers, what else?
+ return _stringToHTML(""+x);
+}
+
+
+/**
+ * Generates a random string of specified length using upper-case letters, lower-case letters, and numbers.
+ */
+
+var _jrand = new Random(currentTimeMillis());
+
+function randomString(nchars) {
+ var result = '';
+
+ // 48-58 or 65-91 or 97-123 (inclusive-exclusive)
+ // 0-10 or 0-26 or 0-26
+ // 0-62
+
+ for (var i = 0; i < nchars; i++) {
+ var x = _jrand.nextInt(62);
+ var code;
+ if (x < 10) { code = x + 48; }
+ if (x >= 10 && x < 36) { code = x - 10 + 65/*a*/; }
+ if (x >= 36) { code = x - 36 + 97/*A*/; }
+ result += String.fromCharCode(code);
+ }
+ return result;
+}
+
+function md5(x) {
+ return net.appjet.ajstdlib.md5.md5(x);
+}
+
+function randomHash(len) {
+ var x = md5(""+_jrand.nextDouble()*1e12+_jrand.nextDouble()*1e12);
+ if (len) {
+ return String(x).substr(0,len);
+ } else {
+ return x;
+ }
+}
+
+function gzip(x) {
+ return net.appjet.oui.Util.gzip(x)
+}
+
+function isNumeric(x) {
+ return !!(/^\d+$/.test(x));
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/sync.js b/trunk/infrastructure/framework-src/modules/sync.js
new file mode 100644
index 0000000..a222ea0
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/sync.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+jimport("java.util.concurrent.locks.ReentrantLock");
+jimport("net.appjet.oui.GlobalSynchronizer");
+
+/**
+ * synchronously calls a no-argument function.
+ * f may have return values.
+ */
+function callsync(obj, f) {
+ if (!obj._LOCK) {
+ try {
+ appjet.globalLock.lock();
+ if (! obj._LOCK) {
+ obj._LOCK = new ReentrantLock();
+ }
+ } finally {
+ appjet.globalLock.unlock();
+ }
+ }
+ try {
+ obj._LOCK.lock();
+ return f();
+ } finally {
+ obj._LOCK.unlock();
+ }
+}
+
+/**
+ * synchronously calls a no-argument function iff
+ * condition() is true. condition may be called
+ * twice and shouldn't have side-effects.
+ */
+function callsyncIfTrue(obj, condition, f) {
+ if (condition()) {
+ callsync(obj, function() {
+ if (condition()) {
+ f();
+ }
+ });
+ }
+}
+
+/**
+ * returns a function that synchronously calls
+ * f with its own arguments
+ */
+function wrapsync(obj, f, thisArg) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ var wrapper = function() {
+ return f.apply(thisArg, args);
+ }
+ callsync(obj, wrapper);
+ }
+}
+
+function doWithStringLock(lockName, fn) {
+ GlobalSynchronizer.acquire(lockName);
+ try {
+ return fn();
+ }
+ finally {
+ GlobalSynchronizer.release(lockName);
+ }
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/timer.js b/trunk/infrastructure/framework-src/modules/timer.js
new file mode 100644
index 0000000..01be175
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/timer.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("jsutils.*");
+
+jimport("net.appjet.ajstdlib.timer");
+
+function time(name, f) {
+ var t = timer.start(name);
+ try {
+ return f();
+ } finally {
+ t.done();
+ }
+}
+
diff --git a/trunk/infrastructure/framework-src/modules/varz.js b/trunk/infrastructure/framework-src/modules/varz.js
new file mode 100644
index 0000000..0e55d20
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/varz.js
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+jimport("java.util.concurrent.atomic.AtomicInteger");
+
+import("sync");
+
+function varz() {
+ sync.callsyncIfTrue(appjet.cache,
+ function() { return ! appjet.cache.varz; },
+ function() { appjet.cache.varz = {}; });
+ return appjet.cache.varz;
+}
+
+function _getInteger(name) {
+ sync.callsyncIfTrue(varz(),
+ function() { return ! varz()[name] },
+ function() { varz()[name] = new AtomicInteger(0) });
+ return varz()[name];
+}
+
+function incrementInt(name) {
+ _getInteger(name).getAndIncrement();
+}
+
+function addToInt(name, count) {
+ _getInteger(name).getAndAdd(count);
+}
+
+function getSnapshot() {
+ var ret = {};
+ for (var k in varz()) {
+ if (k[0] == '_') {
+ continue;
+ }
+ ret[k] = varz()[k].toString();
+ }
+ return ret;
+}
diff --git a/trunk/infrastructure/framework-src/modules/yuicompressor.js b/trunk/infrastructure/framework-src/modules/yuicompressor.js
new file mode 100644
index 0000000..572cc0d
--- /dev/null
+++ b/trunk/infrastructure/framework-src/modules/yuicompressor.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+jimport("java.lang.System.err")
+jimport("yuicompressor.org.mozilla.javascript.ErrorReporter");
+jimport("com.yahoo.platform.yui.compressor.JavaScriptCompressor")
+jimport("com.yahoo.platform.yui.compressor.CssCompressor")
+jimport("java.io.StringReader");
+jimport("java.io.StringWriter");
+
+/**
+ * Compresses the given JavaScript code into an equivalent, shorter string of code using
+ * YUICompressor. In addition to removing white-space and comments, YUICompressor
+ * does a full semantic parse of the code and renames non-global variables to have
+ * very short names. Scopes that are visible to "eval" and "with" are excluded from
+ * variable renaming, making the operation very safe.
+ * <p>
+ * For example,
+ * yuicompressor.compressJS("function foo() { var longVariableName = 3; return longVariableName }");
+ * produces
+ * "function foo(){var A=3;return A;}"
+ */
+
+function compressJS(code) {
+ function getComplaint(message, sourceName, line, lineSource, lineOffset) {
+ if (line < 0) return message;
+ else return (line+":"+lineOffset+":"+message);
+ }
+ function complaintHandler(func) {
+ return function(message, sourceName, line, lineSource, lineOffset) {
+ return func(getComplaint(message, sourceName, line, lineSource, lineOffset));
+ }
+ }
+ var myErrorReporter = new JavaAdapter(ErrorReporter, {
+ warning: complaintHandler(function (msg) {
+ if (msg.indexOf("Try to use a single 'var' statement per scope.") >= 0)
+ return;
+ err.println("yuicompressor.compressJS warning: "+msg);
+ }),
+ error: complaintHandler(function (msg) {
+ throw new Error("yuicompressor.compressJS error: "+msg);
+ }),
+ runtimeError: complaintHandler(function (msg) {
+ throw new Error("yuicompressor.compressJS error: "+msg);
+ })
+ });
+
+ var munge = true;
+ var verbose = false;
+ var optimize = true;
+ var wrapPos = 100; // characters, no wrap == -1
+ var compressor = new JavaScriptCompressor(new StringReader(code), myErrorReporter);
+ var writer = new StringWriter();
+ compressor.compress(writer, 100, munge, verbose, true, !optimize);
+ return String(writer.toString());
+}
+
+/**
+ * Compresses the given CSS code into an equivalent, shorter string of code using
+ * YUICompressor. Besides removing unnecessary white-space and comments, the operation
+ * performs an assortment of semantics-preserving optimizations. The operation attempts
+ * to preserve common "hacks" that take advantage of browser differences in parsing.
+ */
+
+function compressCSS(code) {
+ var compressor = new CssCompressor(new StringReader(code));
+ var wrapPos = 100; // characters, no wrap == -1
+ var writer = new StringWriter();
+ compressor.compress(writer, wrapPos);
+ return String(writer.toString());
+}
diff --git a/trunk/infrastructure/framework-src/oncomet.js b/trunk/infrastructure/framework-src/oncomet.js
new file mode 100644
index 0000000..b6aeda5
--- /dev/null
+++ b/trunk/infrastructure/framework-src/oncomet.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.cometHandler === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No comet handler defined!");
+}
+
+function _ga(k) {
+ return String(appjet.context.attributes().apply(k));
+}
+
+var _op = String(_ga("cometOperation"));
+switch (_op) {
+ case "connect":
+ serverhandlers.cometHandler("connect", _ga("cometId"));
+ break;
+ case "disconnect":
+ serverhandlers.cometHandler("disconnect", _ga("cometId"));
+ break;
+ case "message":
+ serverhandlers.cometHandler("message", _ga("cometId"), _ga("cometData"));
+ break;
+ default:
+ throw new Packages.net.appjet.oui.ExecutionException("Unknown comet operation: '"+_op+"'");
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onerror.js b/trunk/infrastructure/framework-src/onerror.js
new file mode 100644
index 0000000..f19a85f
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onerror.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.errorHandler === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No error handler defined!");
+}
+
+// default content type for request
+response.setContentType('text/html; charset=utf-8');
+
+serverhandlers.errorHandler(appjet.context.attributes().apply("error"));
diff --git a/trunk/infrastructure/framework-src/onprint.js b/trunk/infrastructure/framework-src/onprint.js
new file mode 100644
index 0000000..8e334fe
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onprint.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.postRequestHandler !== undefined) {
+ serverhandlers.postRequestHandler();
+}
diff --git a/trunk/infrastructure/framework-src/onrequest.js b/trunk/infrastructure/framework-src/onrequest.js
new file mode 100644
index 0000000..d76c8db
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onrequest.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.requestHandler === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No request handler defined!");
+}
+
+// default content type for request
+response.setContentType('text/html; charset=utf-8');
+
+serverhandlers.requestHandler();
diff --git a/trunk/infrastructure/framework-src/onreset.js b/trunk/infrastructure/framework-src/onreset.js
new file mode 100644
index 0000000..24b000a
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onreset.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.resetHandler !== undefined) {
+ serverhandlers.resetHandler();
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onsars.js b/trunk/infrastructure/framework-src/onsars.js
new file mode 100644
index 0000000..31dc8ca
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onsars.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.sarsHandler === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No SARS handler defined.");
+}
+
+if (serverhandlers.sarsHandler !== undefined) {
+ (function() {
+ var ret = serverhandlers.sarsHandler(appjet.context.attributes().apply("sarsRequest"));
+ if (ret)
+ appjet.context.attributes().update("sarsResponse", ret);
+ })()
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onscheduledtask.js b/trunk/infrastructure/framework-src/onscheduledtask.js
new file mode 100644
index 0000000..810c3b5
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onscheduledtask.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+ if (serverhandlers.tasks === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No task handlers defined!");
+ }
+ var taskName = appjet.context.attributes().apply("taskName");
+ if (serverhandlers.tasks[taskName] === undefined) {
+ throw new Packages.net.appjet.oui.NoHandlerException("No handler defined for task: "+taskName);
+ }
+ var taskArgs = appjet.context.attributes().apply("taskArguments");
+ var argsArray = [];
+ if (taskArgs != null) {
+ for (var i = 0; i < taskArgs.length; ++i) {
+ argsArray.push(taskArgs[i]);
+ }
+ }
+ return serverhandlers.tasks[taskName].apply(null, argsArray);
+})(); \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onshutdown.js b/trunk/infrastructure/framework-src/onshutdown.js
new file mode 100644
index 0000000..0243bf6
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onshutdown.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.shutdownHandler !== undefined) {
+ serverhandlers.shutdownHandler();
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onstartup.js b/trunk/infrastructure/framework-src/onstartup.js
new file mode 100644
index 0000000..61feff7
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onstartup.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (serverhandlers.startupHandler !== undefined) {
+ serverhandlers.startupHandler();
+} \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/onsyntaxerror.js b/trunk/infrastructure/framework-src/onsyntaxerror.js
new file mode 100644
index 0000000..7129a16
--- /dev/null
+++ b/trunk/infrastructure/framework-src/onsyntaxerror.js
@@ -0,0 +1,17 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+printSyntaxError(); \ No newline at end of file
diff --git a/trunk/infrastructure/framework-src/postamble.js b/trunk/infrastructure/framework-src/postamble.js
new file mode 100644
index 0000000..76fa766
--- /dev/null
+++ b/trunk/infrastructure/framework-src/postamble.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+_appjethidden_.finishImports();
+
diff --git a/trunk/infrastructure/framework-src/preamble.js b/trunk/infrastructure/framework-src/preamble.js
new file mode 100644
index 0000000..40f6845
--- /dev/null
+++ b/trunk/infrastructure/framework-src/preamble.js
@@ -0,0 +1,325 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// appjetContext.cache_requestCache()._t_start = (new Date()).valueOf();
+var _appjethidden_ = {};
+var serverhandlers = { tasks: {} };
+
+/*
+ * @overview
+ *
+ * AppJet standard library preamble.
+ *
+ * This is run at the beginning of every request, right after all
+ * native calls are loaded into appjetContext. This file is run
+ * in the same scope as the app, the global scope, which is also
+ * accessible from all modules.
+ */
+
+//----------------------------------------------------------------
+// delete pesky rhino built-in string stuff
+//----------------------------------------------------------------
+(function() {
+ // rhino strings come with a bunch of random "html helpers"
+ // that we don't want
+ var htmlStuff = ["bold", "italics", "fixed", "strike",
+ "small", "big", "sub", "fontsize", "fontcolor", "link",
+ "anchor", "sup", "blink"];
+ for(var i in htmlStuff) {
+ delete String.prototype[htmlStuff[i]];
+ }
+})();
+
+//----------------------------------------------------------------
+// module implementation
+//----------------------------------------------------------------
+
+(function(globalScope) {
+
+ //----------------------------------------------------------------
+ // Utility Functions
+ //----------------------------------------------------------------
+ function appjetContext() {
+ return net.appjet.oui.ExecutionContextUtils.currentContext();
+ }
+ function internalError(m) {
+ throw new Error("AppJet Internal Error: "+m);
+ }
+ function apiError(m) {
+ throw new Error("AppJet API Error: "+m);
+ }
+ function newScope() {
+ var o = new Object();
+ o.__parent__ = null;
+ o.__proto__ = globalScope;
+ return o;
+ }
+ _appjethidden_._debugMessage = function(m) {
+ //java.lang.System.out.println(m);
+ };
+ var debug = _appjethidden_._debugMessage;
+ function copySymbol(srcName, symName, src, dst, dstSymName) {
+ if (!src.hasOwnProperty(symName)) {
+ apiError("Import error: module \""+srcName+"\" does not contain the symbol \""+symName+"\".");
+ }
+ if (symName.charAt(0) == '_') {
+ apiError("Import error: cannot import symbol \""+symName+"\" because it is private (begins with _)");
+ }
+ debug(" | copying symbol ["+symName+"]");
+ dst[dstSymName || symName] = src[symName];
+ }
+ function copyPublicSymbols(src, dst) {
+ for (k in src) {
+ if (src.hasOwnProperty(k) && (k.length > 0) && (k.charAt(0) != '_')) {
+ copySymbol('', k, src, dst);
+ }
+ }
+ }
+
+ // Module import cache... hidden from other scopes.
+ var moduleObjects = {};
+ var modulesBeingLoaded = {};
+
+ /*--------------------------------------------------------------------------------
+ * loadModule():
+ * Evaluates moduleName in its own private scope, then copies its public identifiers
+ * into a new scope. This new scope is stored in moduleObjects[moduleName] for future use
+ * by import()s.
+ *
+ * If moduleName is currently being loaded (because we are in the middle of another loadModule()
+ * higher in the call stack), then this function does noething, on the assumption
+ * that moduleName will eventually be loaded anyway. Therefore, it cannot be assumed that
+ * moduleName is done being loaded when loadModule() returns, only that it eventually will be
+ * loaded when all loadModule calls return up the call stack.
+ *--------------------------------------------------------------------------------*/
+ function loadModule(moduleName) {
+ if (modulesBeingLoaded[moduleName]) {
+ // This is OK. The module will be loaded eventually.
+ return;
+ }
+ if (moduleObjects[moduleName]) {
+ return;
+ }
+ modulesBeingLoaded[moduleName] = true;
+ try {
+ debug("loadModule: "+moduleName);
+
+ var modulePrivateScope =
+ Packages.net.appjet.ajstdlib.ajstdlib.runModuleInNewScope(
+ appjetContext(), moduleName.split('.').join('/'));
+
+ if (!modulePrivateScope) {
+ // moduleName is not a module. This is normal, because when someone calls
+ // import("foo.bar"), we dont know if bar is a module or an identifier in the foo module.
+ delete modulesBeingLoaded[moduleName];
+ return;
+ }
+ // Thinking this could be useful:
+ // modulePrivateScope['__MODULE_NAME__'] = moduleName;
+ var moduleObj = newScope();
+ copyPublicSymbols(modulePrivateScope, moduleObj);
+ moduleObjects[moduleName] = moduleObj;
+ } finally {
+ delete modulesBeingLoaded[moduleName];
+ }
+ }
+
+ /*--------------------------------------------------------------------------------
+ * importSingleModule():
+ *
+ * Takes a single moduleName (like "etherpad.foo.bar.baz") and creates the identifier "baz"
+ * in dstScope, referencing the module etherpad.foo.bar.baz.
+ *
+ * This function is called one or more times by importPath(). Note that importPath() is more like
+ * the import() function that modules ses.
+ *--------------------------------------------------------------------------------*/
+ function importSingleModule(moduleName, dstScope) {
+ debug("importSingleModule: "+moduleName);
+ if (typeof(moduleName) != 'string') {
+ apiError("modules should be referred to with string, not "+typeof(moduleName));
+ }
+
+ var moduleObj = moduleObjects[moduleName]; // public module scope
+ if (!moduleObj) {
+ return false;
+ }
+
+ var importedName = moduleName;
+ if (importedName.indexOf(".") != -1) {
+ importedName = importedName.split(".").slice(-1)[0];
+ }
+ dstScope[importedName] = moduleObj;
+ return true;
+ }
+
+ /*--------------------------------------------------------------------------------
+ * importPath():
+ * takes a modulePath (like "a.b.c.{d,e,f}" or "a.b.*" or just "a.b" or "a") and
+ * repeatedly calls importSingleModule() as necessary, copying public symbols into dst.
+ *--------------------------------------------------------------------------------*/
+ function importPath(modulePath, dst) {
+ debug("importPath: "+modulePath);
+
+ // Two possibilties:
+ // 1. import the exact module and that's it.
+ //
+ // 2. module contains a "." and we need to import up to the
+ // last ., and then import a name (or set of names) from it.
+
+ // first try case 1:
+ var ok = importSingleModule(modulePath, dst);
+ if (ok) {
+ return;
+ }
+
+ if (modulePath.indexOf(".") == -1) {
+ throw new Error("Module does not exist: "+modulePath);
+ }
+
+ // now try case 2:
+ var tempDst = newScope();
+ var moduleName = modulePath.split('.').slice(0, -1).join('.');
+ var importedName = modulePath.split('.').slice(-1)[0];
+ var lastName = modulePath.split('.').slice(-2, -1)[0];
+
+ ok = importSingleModule(moduleName, tempDst);
+ if (!ok) {
+ throw new Error("Neither module exists: "+moduleName+", "+modulePath);
+ }
+
+ if (!tempDst[lastName]) {
+ internalError("import failed for "+moduleName+"|"+importedName+". This could be an appjet bug.");
+ }
+ if (importedName == "*") {
+ copyPublicSymbols(tempDst[lastName], dst);
+ } else if (importedName.match(/^\{.*\}$/)) {
+ importedName.slice(1,-1).split(',').forEach(function(sym) {
+ if (sym.match(/^.*=>.*$/)) {
+ copySymbol(moduleName, sym.split("=>")[0], tempDst[lastName], dst, sym.split("=>")[1]);
+ } else {
+ copySymbol(moduleName, sym, tempDst[lastName], dst);
+ }
+ });
+ } else {
+ copySymbol(moduleName, importedName, tempDst[lastName], dst);
+ }
+ }
+
+ //----------------------------------------------------------------
+ // scheduling
+ //----------------------------------------------------------------
+
+ var scheduledImports = [];
+
+ function scheduleImportPath(p, dst) {
+ scheduledImports.push([p, dst]);
+ }
+
+ function runScheduledImports() {
+ scheduledImports.forEach(function(x) {
+ importPath(x[0], x[1]);
+ });
+ }
+
+ //----------------------------------------------------------------
+ // The global import function
+ //----------------------------------------------------------------
+
+ _appjethidden_.importsAllowed = true;
+
+ globalScope['import'] = function(path1, path2, etc) {
+ if (!_appjethidden_.importsAllowed) {
+ throw Error("Imports are finished. No more imports are allowed.");
+ }
+
+ var dstScope = this;
+ if (arguments.length < 1) {
+ apiError("importModule() takes the name of at least one module as an argument.");
+ }
+ for (var i = 0; i < arguments.length; i++) {
+ var path = arguments[i];
+ debug("scheduling import: "+path);
+ scheduleImportPath(path, dstScope);
+ // evaluate all modules in this path.
+ var parts = path.split('.');
+ for (var j = 0; j < parts.length; j++) {
+ var moduleName = parts.slice(0,j+1).join('.');
+ loadModule(moduleName);
+ }
+ }
+ };
+
+ _appjethidden_.finishImports = function() {
+ debug("Running scheduled imports...");
+ runScheduledImports();
+ _appjethidden_.importsAllowed = false;
+ };
+
+ //----------------------------------------------------------------
+ // jimport
+ //----------------------------------------------------------------
+ function _jimportSinglePackage(pname, dstScope) {
+ //_appjethidden_._debugMessage("_jimportSinglePackage: "+pname);
+ // TODO: support "*" and "{}" syntax like scala.
+ var src = Packages;
+ var srcParent = null;
+ var localName = pname.split(".").pop();
+ var soFar = '';
+
+ pname.split(".").forEach(function(x) {
+ soFar += x+'.';
+ if (!src[x]) {
+ throw ('Could not find java package/class: '+soFar);
+ } else {
+ //_appjethidden_._debugMessage("descenting into "+src+"["+x+"]");
+ srcParent = src;
+ src = src[x];
+ }
+ });
+
+ if (String(src).indexOf('function') == 0) {
+ // TODO: checking String(src).indexOf('function') is rather brittle.
+ // is there a cleaner way?
+ // TODO: this only works on static functions... so make sure
+ // src[x] is a static function!
+ dstScope[localName] = function() {
+ return src.apply(srcParent, Array.prototype.slice.call(arguments));
+ };
+ } else {
+ // importing a regular java class
+ dstScope[localName] = src;
+ }
+ }
+
+ /**
+ * Import a java package over LiveConnect.
+ */
+ globalScope['jimport'] = function() {
+ var dstScope = this;
+ for (var i = 0; i < arguments.length; i++) {
+ var pname = arguments[i].split(".").pop();
+ _jimportSinglePackage(arguments[i], dstScope);
+ }
+ };
+
+ //----------------------------------------------------------------
+ // {appjet, request, response} imported by default
+ //----------------------------------------------------------------
+ globalScope['import'].call(globalScope,
+ "global.appjet.appjet", "global.request.request", "global.response.response");
+
+})(this);
+
diff --git a/trunk/infrastructure/framework-src/syntaxerror.js b/trunk/infrastructure/framework-src/syntaxerror.js
new file mode 100644
index 0000000..801066b
--- /dev/null
+++ b/trunk/infrastructure/framework-src/syntaxerror.js
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import("exceptionutils");
+
+function printSyntaxError() {
+ var ex = appjet.context.attributes().apply("error");
+
+ //java.lang.System.out.println("Syntax error: "+ex);
+
+ if (response.isDefined) {
+ response.reset();
+ response.setContentType('text/html; charset=utf-8');
+ response.write(exceptionutils.getStackTraceFullpage(ex));
+ } else {
+ java.lang.System.out.println("Syntax error: "+exceptionutils.getStackTracePlain(ex));
+ }
+}
+